import Foundation
import ComplyCubeMobileSDK

/// Builds CustomerInformationStage directly from the raw map (parity with Android).
public final class CustomerInfoParser: StageParser {

    private let fieldMapper: CustomerInfoFieldMapper
    private let templateProcessor: MetadataTemplateProcessor

    public init(fieldMapper: CustomerInfoFieldMapper = .init(),
                templateProcessor: MetadataTemplateProcessor = .init()) {
        self.fieldMapper = fieldMapper
        self.templateProcessor = templateProcessor
    }

    public func canParse(_ input: Any) -> Bool {
        (input as? [String: Any]).map { ($0["name"] as? String) == "customerInfo" } ?? false
    }

    public func parse(_ input: Any) throws -> ComplyCubeMobileSDKStage? {
        let json = input as! [String: Any]
        let builder = CustomerInformationStageBuilder()

        guard let title = json["title"] as? String else {
            throw ConfigurationError.missing(field: "customerInfo.title")
        }
        builder.withTitle(title: title)

        let fieldsArray = json["customerInfoFields"] as? [Any] ?? []

        // Discover templates at top-level or nested under any field object
        var metadataTemplates: [[String: Any]] =
            (json["metadataTemplates"] as? [[String: Any]]) ?? []
        if metadataTemplates.isEmpty {
            for field in fieldsArray {
                if let m = field as? [String: Any],
                   let nested = m["metadataTemplates"] as? [[String: Any]],
                   !nested.isEmpty {
                    metadataTemplates = nested
                    break
                }
            }
        }

        var fields: [CustomerInformationField] = []

        // 1) simple string fields
        for item in fieldsArray {
            if let s = item as? String, let mapped = fieldMapper.mapStringField(s) {
                fields.append(mapped)
            }
        }

        // 2) object blocks (details / metadata)
        for item in fieldsArray {
            guard let dict = item as? [String: Any] else { continue }
            parseComplexField(dict, out: &fields, templates: metadataTemplates)
        }

        print("CustomerInfo: generated \(fields.count) fields")
        fields.enumerated().forEach { idx, f in
            print("[\(idx)] \(f)")
        }

        builder.withQuestions(fields: fields)
        return builder.build()
    }

    // MARK: - Complex field parsing

    private func parseComplexField(_ field: [String: Any],
                                   out: inout [CustomerInformationField],
                                   templates: [[String: Any]]) {

        // details
        if let details = field["details"] as? [[String: Any]] {
            for detail in details {
                if let person = detail["person"] as? [Any] {
                    let mapped = fieldMapper.mapPersonFields(person)
                    if !mapped.isEmpty { out.append(.details(.person(mapped))) }
                }
                if let company = detail["company"] as? [Any] {
                    let mapped = fieldMapper.mapCompanyFields(company)
                    if !mapped.isEmpty { out.append(.details(.company(mapped))) }
                }
            }
        }

        // metadata
        if let metadata = field["metadata"] as? [[String: Any]] {
            parseMetadata(metadata, out: &out, templates: templates)
        }
    }

    private func parseMetadata(_ list: [[String: Any]],
                               out: inout [CustomerInformationField],
                               templates: [[String: Any]]) {
        var countrySelectKey = ""

        for meta in list {
            guard let key = meta["key"] as? String,
                  let compRaw = meta["componentType"] as? String else { continue }

            let question = meta["question"] as? String ?? ""
            let componentType = parseComponentType(compRaw)

            // Optional format
            var formatModel: Format? = nil
            if let fmt = meta["format"] as? [String: Any] {
                formatModel = decodeCodable(Format.self, from: fmt)
            }

            // Options
            var options: [CustomerInformationField.Value : CustomerInformationField.Label]? = nil
            if componentType != .countries {
                if let arr = meta["options"] as? [[String: Any]] {
                    var dict: [String: String] = [:]
                    for item in arr {
                        let label = item["label"] as? String ?? ""
                        let value = item["value"] as? String ?? ""
                        dict[value] = label
                    }
                    options = dict.isEmpty ? nil : dict
                }
            }

            let required = meta["required"] as? Bool ?? false
            let description = meta["description"] as? String
            let constraint = (meta["constraint"] as? [String: Any])?["expression"] as? String

            if componentType == .countries {
                countrySelectKey = key

                // Build filtered countries list and country options
                let filteredCountries = parseCountrySelection(meta)
                let countryOptions: [CustomerInformationField.Value : CustomerInformationField.Label] =
                    Dictionary(uniqueKeysWithValues: filteredCountries.map { ($0.code, $0.code) })

                out.append(.metaData(
                    key: key,
                    question: question,
                    componentType: componentType,
                    format: formatModel,
                    options: countryOptions,
                    required: required,
                    description: description,
                    placeholder: nil,
                    constraint: constraint,
                    dataList: nil
                ))

                // Inject country-specific TIN templates
                templateProcessor.processCountryTemplates(
                    filteredCountries: filteredCountries,
                    countrySelectKey: countrySelectKey,
                    metadataTemplates: templates,
                    customerInfoFields: &out
                )
            } else {
                out.append(.metaData(
                    key: key,
                    question: question,
                    componentType: componentType,
                    format: formatModel,
                    options: options,
                    required: required,
                    description: description,
                    placeholder: nil,
                    constraint: constraint,
                    dataList: nil
                ))
            }
        }
    }

    // MARK: - Helpers

    private func parseComponentType(_ type: String) -> ComponentType {
        switch type.uppercased() {
        case "SHORT_ANSWER":         return .shortAnswer
        case "PARAGRAPH":            return .paragraph
        case "SINGLE_CHOICE":        return .singleSelection
        case "MULTI_CHOICE":         return .multipleChoice
        case "MULTI_SELECT_COUNTRY": return .countries   // matches Android
        case "DATE":                 return .date
        case "COUNTRY":              return .country
        case "NUMBER":               return .phoneNumber
        case "PHONE":                return .phoneNumber
        default:                     return .shortAnswer
        }
    }

    private func decodeCodable<T: Decodable>(_ type: T.Type, from dict: [String: Any]) -> T? {
        guard JSONSerialization.isValidJSONObject(dict),
              let data = try? JSONSerialization.data(withJSONObject: dict) else { return nil }
        return try? JSONDecoder().decode(T.self, from: data)
    }

    /// Replicates Android's `parseCountrySelection`:
    /// - If `countries` is missing → all countries.
    /// - If mode = inclusion  → only listed codes.
    /// - If mode = exclusion  → all except listed codes.
    private func parseCountrySelection(_ metadataMap: [String: Any]) -> [Country] {
        // All countries via SDK utilities
        let allCodes: [String] = ComplyUtils.allCountries()
        let allCountries: [Country] = countries(from: allCodes)

        guard let countriesObj = metadataMap["countries"] as? [String: Any] else {
            return allCountries
        }

        let mode = (countriesObj["mode"] as? String) ?? "inclusion"
        let list = (countriesObj["list"] as? [Any])?.compactMap { $0 as? String } ?? []
        let included = countries(from: list)

        if mode == "exclusion" {
            let excluded = Set(included.map { $0.code })
            return allCountries.filter { !excluded.contains($0.code) }
        } else {
            return included
        }
    }

    /// Converts ISO country codes into SDK `Country` models using the extractor.
    private func countries(from codes: [String]) -> [Country] {
        let extractor = CountryExtractor()
        extractor.extractCountries(codes)
        return extractor.getAllExtractedCountries()
    }
}
