using System.Text.Json;
using Anthropic.SDK;
using Anthropic.SDK.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RealCV.Application.Helpers;
using RealCV.Application.Interfaces;
using RealCV.Application.Models;
using RealCV.Infrastructure.Configuration;
using RealCV.Infrastructure.Helpers;

namespace RealCV.Infrastructure.Services;

public sealed class AICompanyNameMatcherService : ICompanyNameMatcherService
{
    private readonly AnthropicClient _anthropicClient;
    private readonly ILogger<AICompanyNameMatcherService> _logger;

    private const string SystemPrompt = """
        You are a UK company name matching expert. Your task is to determine if a company name
        from a CV matches any of the official company names from Companies House records.

        You understand:
        - Trading names vs registered names (e.g., "Tesco" = "TESCO PLC")
        - Subsidiaries vs parent companies (e.g., "ASDA" might work for "ASDA STORES LIMITED")
        - Common abbreviations (Ltd = Limited, PLC = Public Limited Company, CiC = Community Interest Company)
        - That completely different words mean different companies (e.g., "Families First" ≠ "Families Against Conformity")

        You must respond ONLY with valid JSON, no other text or markdown.
        """;

    private const string MatchingPrompt = """
        Compare the company name from a CV against official Companies House records.

        CV Company Name: "{CV_COMPANY}"
        {INDUSTRY_CONTEXT}
        Companies House Candidates:
        {CANDIDATES}

        Determine which candidate (if any) is the SAME company as the CV entry.

        Matching Guidelines:
        1. MATCH if the CV name is the same organisation as a candidate (even if registered name differs):
           - "Boots" → "BOOTS UK LIMITED" ✓ (trading name = registered company)
           - "Boots" → "THE BOOTS COMPANY PLC" ✓ (trading name = parent company)
           - "Tesco" → "TESCO PLC" ✓ (trading name = registered name)
           - "ASDA" → "ASDA STORES LIMITED" ✓ (brand = operating company)
           - "Legal & General" → "LEGAL & GENERAL GROUP PLC" ✓ (brand = holding company)
           - "Checkout.com" → "CHECKOUT.COM PAYMENTS LIMITED" ✓ (exact match)
           - "EY UK" → "ERNST & YOUNG LLP" ✓ (trading name = partnership)
           - "Royal Bank of Scotland" → "THE ROYAL BANK OF SCOTLAND PUBLIC LIMITED COMPANY" ✓

        2. DO NOT MATCH if the candidate adds significant DIFFERENT words that indicate a different business:
           - "Boots" ≠ "BOOTS AND BEARDS" ✗ (pharmacy chain is NOT a barber/grooming business)
           - "Legal & General" ≠ "LEGAL LIMITED" ✗ (major insurer is NOT a generic "legal" company)
           - "Checkout.com" ≠ "XN CHECKOUT LIMITED" ✗ (fintech is NOT an unrelated checkout company)
           - "EY UK" ≠ "EY UK GDPR REPRESENTATIVE LIMITED" ✗ (main employer, not a subsidiary)

        3. KEY DISTINCTION - Geographic/legal suffixes are OK, but new business words are NOT:
           - "Boots" → "BOOTS UK LIMITED" ✓ (UK is just geographic qualifier)
           - "Boots" → "BOOTS AND BEARDS" ✗ (BEARDS indicates different business)
           - "Meridian Holdings" → "MERIDIAN (THE ORIGINAL) LIMITED" ✗ ("THE ORIGINAL" suggests different business)
           - "Paramount Consulting UK" → "PARAMOUNT LIMITED" ✗ (missing "Consulting" - different type)
           - "Apex Technology Partners" → "APEX LIMITED" ✗ (missing "Technology Partners")

        4. Legal suffixes (Ltd, Limited, PLC, LLP, CiC) should be ignored when comparing names

        5. Adding "THE", "GROUP", "UK", or "HOLDINGS" to a name doesn't make it a different company

        6. When the CV mentions a well-known brand, prefer the main operating/holding company over obscure matches

        7. If INDUSTRY CONTEXT is provided, use it to reject candidates clearly in different industries

        CRITICAL: Return the COMPLETE company number exactly as shown (e.g., "SC083026", "02366995").
        Do NOT truncate or abbreviate the company number.

        Respond with this exact JSON structure:
        {
            "bestMatchCompanyNumber": "COMPLETE company number from the list above, or 'NONE' if no valid match",
            "confidenceScore": number (0-100, where 100 = certain match, 0 = no match),
            "matchType": "Exact|TradingName|Subsidiary|Parent|NoMatch",
            "reasoning": "brief explanation"
        }
        """;

    public AICompanyNameMatcherService(
        IOptions<AnthropicSettings> settings,
        ILogger<AICompanyNameMatcherService> logger)
    {
        _logger = logger;
        _anthropicClient = new AnthropicClient(settings.Value.ApiKey);
    }

    public async Task<SemanticMatchResult?> FindBestMatchAsync(
        string cvCompanyName,
        List<CompanyCandidate> candidates,
        string? industryHint = null,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(cvCompanyName) || candidates.Count == 0)
        {
            return null;
        }

        _logger.LogDebug("Using AI to match '{CVCompany}' against {Count} candidates (industry: {Industry})",
            cvCompanyName, candidates.Count, industryHint ?? "unknown");

        try
        {
            // Format candidates with company number prominently displayed to prevent truncation
            var candidatesText = string.Join("\n", candidates.Select((c, i) =>
                $"[{c.CompanyNumber}] {c.CompanyName} (Status: {c.CompanyStatus ?? "Unknown"})"));

            // Add industry context if available
            var industryContext = string.IsNullOrEmpty(industryHint)
                ? ""
                : $"Industry Context: This is a well-known brand in {industryHint}. Reject candidates clearly in different industries.\n";

            var prompt = MatchingPrompt
                .Replace("{CV_COMPANY}", cvCompanyName)
                .Replace("{INDUSTRY_CONTEXT}", industryContext)
                .Replace("{CANDIDATES}", candidatesText);

            var messages = new List<Message>
            {
                new(RoleType.User, prompt)
            };

            var parameters = new MessageParameters
            {
                Model = "claude-sonnet-4-20250514",
                MaxTokens = 1024,
                Messages = messages,
                System = [new SystemMessage(SystemPrompt)]
            };

            var response = await _anthropicClient.Messages.GetClaudeMessageAsync(parameters, cancellationToken);

            var responseText = response.Content
                .OfType<TextContent>()
                .FirstOrDefault()?.Text;

            if (string.IsNullOrWhiteSpace(responseText))
            {
                _logger.LogWarning("AI returned empty response for company matching");
                return null;
            }

            responseText = JsonResponseHelper.CleanJsonResponse(responseText);

            var aiResponse = JsonSerializer.Deserialize<AIMatchResponse>(responseText, JsonDefaults.CamelCase);

            if (aiResponse is null)
            {
                _logger.LogWarning("Failed to deserialize AI response: {Response}", responseText);
                return null;
            }

            _logger.LogDebug("AI match result: {CompanyNumber} with {Score}% confidence - {Reasoning}",
                aiResponse.BestMatchCompanyNumber, aiResponse.ConfidenceScore, aiResponse.Reasoning);

            // Find the matched candidate
            // Lower threshold to 30 - we have fuzzy validation as backup
            if (aiResponse.BestMatchCompanyNumber == "NONE" || aiResponse.ConfidenceScore < 30)
            {
                return new SemanticMatchResult
                {
                    CandidateCompanyName = "No match",
                    CandidateCompanyNumber = "NONE",
                    ConfidenceScore = 0,
                    MatchType = "NoMatch",
                    Reasoning = aiResponse.Reasoning
                };
            }

            var matchedCandidate = candidates.FirstOrDefault(c =>
                c.CompanyNumber.Equals(aiResponse.BestMatchCompanyNumber, StringComparison.OrdinalIgnoreCase));

            // If exact match not found, try to find a candidate that starts with the returned number
            // This handles cases where AI truncates "09052626" to "09" or similar
            if (matchedCandidate is null && !string.IsNullOrWhiteSpace(aiResponse.BestMatchCompanyNumber)
                && aiResponse.BestMatchCompanyNumber != "NONE")
            {
                var partialMatch = candidates.FirstOrDefault(c =>
                    c.CompanyNumber.StartsWith(aiResponse.BestMatchCompanyNumber, StringComparison.OrdinalIgnoreCase));

                if (partialMatch is not null)
                {
                    _logger.LogDebug("AI returned partial company number '{Partial}', matched to full number '{Full}'",
                        aiResponse.BestMatchCompanyNumber, partialMatch.CompanyNumber);
                    matchedCandidate = partialMatch;
                }
                else
                {
                    // Try reverse - maybe AI returned a longer string that contains the actual number
                    var reverseMatch = candidates.FirstOrDefault(c =>
                        aiResponse.BestMatchCompanyNumber.Contains(c.CompanyNumber, StringComparison.OrdinalIgnoreCase));

                    if (reverseMatch is not null)
                    {
                        _logger.LogDebug("AI returned string containing company number '{Number}'",
                            reverseMatch.CompanyNumber);
                        matchedCandidate = reverseMatch;
                    }
                }
            }

            if (matchedCandidate is null)
            {
                _logger.LogWarning("AI returned company number '{Number}' not in candidates list. Candidates: {Candidates}",
                    aiResponse.BestMatchCompanyNumber,
                    string.Join(", ", candidates.Select(c => c.CompanyNumber)));
                return null;
            }

            return new SemanticMatchResult
            {
                CandidateCompanyName = matchedCandidate.CompanyName,
                CandidateCompanyNumber = matchedCandidate.CompanyNumber,
                ConfidenceScore = aiResponse.ConfidenceScore,
                MatchType = aiResponse.MatchType,
                Reasoning = aiResponse.Reasoning
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "AI company matching failed for '{CVCompany}'", cvCompanyName);
            return null; // Fall back to fuzzy matching
        }
    }

    private const string CompoundNamePrompt = """
        Analyze this company name from a CV and determine if it refers to ONE company or MULTIPLE companies.

        Company name: "{COMPANY_NAME}"

        Examples:
        - "Ernst & Young" → ONE company (it's the full name of the accounting firm)
        - "Marks & Spencer" → ONE company (it's the full name of the retailer)
        - "ASDA/WALMART" → TWO companies: ["ASDA", "WALMART"] (person worked at both or it's showing ownership)
        - "Corus & Laura Ashley Hotels" → TWO companies: ["Corus", "Laura Ashley Hotels"] (different industries)
        - "PwC" → ONE company
        - "Deloitte and Touche" → ONE company (historical name of Deloitte)
        - "BMW Group Ireland" → ONE company
        - "Tesco Stores and Distribution" → ONE company (departments of same company)

        Rules:
        1. Well-known company names with "&" or "and" are SINGLE companies (Ernst & Young, Marks & Spencer, Procter & Gamble)
        2. A "/" usually indicates multiple companies or ownership relationship
        3. If the parts are in completely different industries, they're likely separate companies
        4. If one part is clearly a subsidiary/department of the other, treat as ONE company

        Respond with ONLY valid JSON:
        {
            "isSingleCompany": boolean,
            "companies": ["company1", "company2"] or ["single company name"],
            "reasoning": "brief explanation"
        }
        """;

    public async Task<List<string>?> ExtractCompanyNamesAsync(
        string companyName,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(companyName))
        {
            return null;
        }

        _logger.LogDebug("Using AI to check if '{CompanyName}' is a compound name", companyName);

        try
        {
            var prompt = CompoundNamePrompt.Replace("{COMPANY_NAME}", companyName);

            var messages = new List<Message>
            {
                new(RoleType.User, prompt)
            };

            var parameters = new MessageParameters
            {
                Model = "claude-3-5-haiku-20241022",
                MaxTokens = 256,
                Messages = messages,
                System = [new SystemMessage("You are a company name parser. Respond only with valid JSON.")]
            };

            var response = await _anthropicClient.Messages.GetClaudeMessageAsync(parameters, cancellationToken);

            var responseText = response.Content
                .OfType<TextContent>()
                .FirstOrDefault()?.Text;

            if (string.IsNullOrWhiteSpace(responseText))
            {
                _logger.LogWarning("AI returned empty response for compound name check");
                return null;
            }

            responseText = JsonResponseHelper.CleanJsonResponse(responseText);

            var result = JsonSerializer.Deserialize<CompoundNameResponse>(responseText, JsonDefaults.CamelCase);

            if (result is null)
            {
                _logger.LogWarning("Failed to deserialize compound name response: {Response}", responseText);
                return null;
            }

            _logger.LogDebug("AI compound name result: IsSingle={IsSingle}, Companies=[{Companies}], Reasoning={Reasoning}",
                result.IsSingleCompany, string.Join(", ", result.Companies ?? []), result.Reasoning);

            if (result.IsSingleCompany || result.Companies is null || result.Companies.Count < 2)
            {
                return null; // Single company, no splitting needed
            }

            return result.Companies;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "AI compound name detection failed for '{CompanyName}'", companyName);
            return null;
        }
    }

    private sealed class CompoundNameResponse
    {
        public bool IsSingleCompany { get; set; }
        public List<string>? Companies { get; set; }
        public string? Reasoning { get; set; }
    }
}
