package x509

import (
	"encoding/json"
	"errors"

	"github.com/zmap/zcrypto/encoding/asn1"
)

type QCStatementASN struct {
	StatementID   asn1.ObjectIdentifier
	StatementInfo asn1.RawValue `asn1:"optional"`
}

func (s *QCStatementASN) MarshalJSON() ([]byte, error) {
	aux := struct {
		ID    string `json:"id,omitempty"`
		Value []byte `json:"value,omitempty"`
	}{
		ID:    s.StatementID.String(),
		Value: s.StatementInfo.Bytes,
	}
	return json.Marshal(&aux)
}

type QCStatementsASN struct {
	QCStatements []QCStatementASN
}

// ETSI OIDS from https://www.etsi.org/deliver/etsi_en/319400_319499/31941205/02.02.03_20/en_31941205v020203a.pdf
var (
	oidEtsiQcsQcCompliance      = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 1}
	oidEtsiQcsQcLimitValue      = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 2}
	oidEtsiQcsQcRetentionPeriod = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 3}
	oidEtsiQcsQcSSCD            = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 4}
	oidEtsiQcsQcEuPDS           = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 5}
	oidEtsiQcsQcType            = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6}
	oidEtsiQcsQcCCLegislation   = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 7}
	oidEtsiQcsQctEsign          = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6, 1}
	oidEtsiQcsQctEseal          = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6, 2}
	oidEtsiQcsQctWeb            = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6, 3}
)

type QCStatements struct {
	StatementIDs     []string            `json:"ids,omitempty"`
	ParsedStatements *ParsedQCStatements `json:"parsed,omitempty"`
}

type ParsedQCStatements struct {
	ETSICompliance  []bool          `json:"etsi_compliance,omitempty"`
	SSCD            []bool          `json:"sscd,omitempty"`
	Types           []QCType        `json:"types,omitempty"`
	Limit           []MonetaryValue `json:"limit,omitempty"`
	PDSLocations    []PDSLocations  `json:"pds_locations,omitempty"`
	RetentionPeriod []int           `json:"retention_period,omitempty"`
	Legislation     []QCLegistation `json:"legislation,omitempty"`
}

type MonetaryValue struct {
	Currency       string `json:"currency,omitempty"`
	CurrencyNumber int    `json:"currency_number,omitempty"`
	Amount         int    `json:"amount,omitempty"`
	Exponent       int    `json:"exponent,omitempty"`
}

type monetaryValueASNString struct {
	Currency string `asn1:"printable"`
	Amount   int
	Exponent int
}

type monetaryValueASNNumber struct {
	Currency int
	Amount   int
	Exponent int
}

type PDSLocations struct {
	Locations []PDSLocation `json:"locations,omitempty"`
}

type PDSLocation struct {
	URL      string `json:"url,omitempty" asn1:"ia5"`
	Language string `json:"language,omitempty" asn1:"printable"`
}

type QCType struct {
	TypeIdentifiers []asn1.ObjectIdentifier
}

type QCLegistation struct {
	CountryCodes []string `json:"country_codes,omitempty"`
}

func (qt *QCType) MarshalJSON() ([]byte, error) {
	aux := struct {
		Types []string `json:"ids,omitempty"`
	}{
		Types: make([]string, len(qt.TypeIdentifiers)),
	}
	for idx := range qt.TypeIdentifiers {
		aux.Types[idx] = qt.TypeIdentifiers[idx].String()
	}
	return json.Marshal(&aux)
}

func (q *QCStatements) Parse(in *QCStatementsASN) error {
	q.StatementIDs = make([]string, len(in.QCStatements))
	known := ParsedQCStatements{}
	for i, s := range in.QCStatements {
		val := in.QCStatements[i].StatementInfo.FullBytes
		q.StatementIDs[i] = s.StatementID.String()
		if s.StatementID.Equal(oidEtsiQcsQcCompliance) {
			known.ETSICompliance = append(known.ETSICompliance, true)
			if val != nil {
				return errors.New("EtsiQcsQcCompliance QCStatement must not contain a statementInfo")
			}
		} else if s.StatementID.Equal(oidEtsiQcsQcLimitValue) {
			// TODO
			mvs := monetaryValueASNString{}
			mvn := monetaryValueASNNumber{}
			out := MonetaryValue{}
			if _, err := asn1.Unmarshal(val, &mvs); err == nil {
				out.Currency = mvs.Currency
				out.Amount = mvs.Amount
				out.Exponent = mvs.Exponent
			} else if _, err := asn1.Unmarshal(val, &mvn); err == nil {
				out.CurrencyNumber = mvn.Currency
				out.Amount = mvn.Amount
				out.Exponent = mvn.Exponent
			} else {
				return err
			}
			known.Limit = append(known.Limit, out)
		} else if s.StatementID.Equal(oidEtsiQcsQcRetentionPeriod) {
			var retentionPeriod int
			if _, err := asn1.Unmarshal(val, &retentionPeriod); err != nil {
				return err
			}
			known.RetentionPeriod = append(known.RetentionPeriod, retentionPeriod)
		} else if s.StatementID.Equal(oidEtsiQcsQcSSCD) {
			known.SSCD = append(known.SSCD, true)
			if val != nil {
				return errors.New("EtsiQcsQcSSCD QCStatement must not contain a statementInfo")
			}
		} else if s.StatementID.Equal(oidEtsiQcsQcEuPDS) {
			locations := make([]PDSLocation, 0)
			if _, err := asn1.Unmarshal(val, &locations); err != nil {
				return err
			}
			known.PDSLocations = append(known.PDSLocations, PDSLocations{
				Locations: locations,
			})
		} else if s.StatementID.Equal(oidEtsiQcsQcType) {
			typeIds := make([]asn1.ObjectIdentifier, 0)
			if _, err := asn1.Unmarshal(val, &typeIds); err != nil {
				return err
			}
			known.Types = append(known.Types, QCType{
				TypeIdentifiers: typeIds,
			})
		} else if s.StatementID.Equal(oidEtsiQcsQcCCLegislation) {
			countryCodes := make([]string, 0)
			if _, err := asn1.Unmarshal(val, &countryCodes); err != nil {
				return err
			}
			known.Legislation = append(known.Legislation, QCLegistation{
				CountryCodes: countryCodes,
			})
		}
	}
	q.ParsedStatements = &known
	return nil
}
