using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RealCV.Application.DTOs;
using RealCV.Infrastructure.Configuration;

namespace RealCV.Infrastructure.ExternalApis;

public sealed class CompaniesHouseClient
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<CompaniesHouseClient> _logger;
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        PropertyNameCaseInsensitive = true
    };

    public CompaniesHouseClient(
        HttpClient httpClient,
        IOptions<CompaniesHouseSettings> settings,
        ILogger<CompaniesHouseClient> logger)
    {
        _httpClient = httpClient;
        _logger = logger;

        var apiKey = settings.Value.ApiKey;
        var authValue = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{apiKey}:"));
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authValue);
        _httpClient.BaseAddress = new Uri(settings.Value.BaseUrl);
    }

    public async Task<CompaniesHouseSearchResponse?> SearchCompaniesAsync(
        string query,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(query);

        var encodedQuery = Uri.EscapeDataString(query);
        var requestUrl = $"/search/companies?q={encodedQuery}";

        _logger.LogDebug("Searching Companies House for: {Query}", query);

        try
        {
            var response = await _httpClient.GetAsync(requestUrl, cancellationToken);

            if (response.StatusCode == HttpStatusCode.TooManyRequests)
            {
                _logger.LogWarning("Rate limit exceeded for Companies House API");
                throw new CompaniesHouseRateLimitException("Rate limit exceeded. Please try again later.");
            }

            response.EnsureSuccessStatusCode();

            var result = await response.Content.ReadFromJsonAsync<CompaniesHouseSearchResponse>(
                JsonOptions,
                cancellationToken);

            _logger.LogDebug("Found {Count} companies matching query: {Query}",
                result?.Items?.Count ?? 0, query);

            return result;
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
        {
            _logger.LogWarning("Rate limit exceeded for Companies House API");
            throw new CompaniesHouseRateLimitException("Rate limit exceeded. Please try again later.", ex);
        }
    }

    public async Task<CompaniesHouseCompany?> GetCompanyAsync(
        string companyNumber,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(companyNumber);

        var requestUrl = $"/company/{companyNumber}";

        _logger.LogDebug("Fetching company details for: {CompanyNumber}", companyNumber);

        try
        {
            var response = await _httpClient.GetAsync(requestUrl, cancellationToken);

            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                _logger.LogDebug("Company not found: {CompanyNumber}", companyNumber);
                return null;
            }

            if (response.StatusCode == HttpStatusCode.TooManyRequests)
            {
                _logger.LogWarning("Rate limit exceeded for Companies House API");
                throw new CompaniesHouseRateLimitException("Rate limit exceeded. Please try again later.");
            }

            response.EnsureSuccessStatusCode();

            var result = await response.Content.ReadFromJsonAsync<CompaniesHouseCompany>(
                JsonOptions,
                cancellationToken);

            _logger.LogDebug("Retrieved company: {CompanyName} ({CompanyNumber})",
                result?.CompanyName, companyNumber);

            return result;
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
        {
            _logger.LogWarning("Rate limit exceeded for Companies House API");
            throw new CompaniesHouseRateLimitException("Rate limit exceeded. Please try again later.", ex);
        }
    }

    public async Task<CompaniesHouseOfficersResponse?> GetOfficersAsync(
        string companyNumber,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(companyNumber);

        var requestUrl = $"/company/{companyNumber}/officers";

        _logger.LogDebug("Fetching officers for company: {CompanyNumber}", companyNumber);

        try
        {
            var response = await _httpClient.GetAsync(requestUrl, cancellationToken);

            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                _logger.LogDebug("No officers found for company: {CompanyNumber}", companyNumber);
                return null;
            }

            if (response.StatusCode == HttpStatusCode.TooManyRequests)
            {
                _logger.LogWarning("Rate limit exceeded for Companies House API");
                throw new CompaniesHouseRateLimitException("Rate limit exceeded. Please try again later.");
            }

            response.EnsureSuccessStatusCode();

            var result = await response.Content.ReadFromJsonAsync<CompaniesHouseOfficersResponse>(
                JsonOptions,
                cancellationToken);

            _logger.LogDebug("Retrieved {Count} officers for company {CompanyNumber}",
                result?.Items?.Count ?? 0, companyNumber);

            return result;
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
        {
            _logger.LogWarning("Rate limit exceeded for Companies House API");
            throw new CompaniesHouseRateLimitException("Rate limit exceeded. Please try again later.", ex);
        }
    }
}

// DTOs for Companies House API responses
public sealed record CompaniesHouseSearchResponse
{
    public int TotalResults { get; init; }
    public int ItemsPerPage { get; init; }
    public int StartIndex { get; init; }
    public List<CompaniesHouseSearchItem> Items { get; init; } = [];
}

public sealed record CompaniesHouseSearchItem
{
    public required string CompanyNumber { get; init; }
    public required string Title { get; init; }
    public string? CompanyStatus { get; init; }
    public string? CompanyType { get; init; }
    public string? DateOfCreation { get; init; }
    public string? DateOfCessation { get; init; }
    public CompaniesHouseAddress? Address { get; init; }
    public string? AddressSnippet { get; init; }
    public List<string>? SicCodes { get; init; }
}

public sealed record CompaniesHouseCompany
{
    public required string CompanyNumber { get; init; }
    public required string CompanyName { get; init; }
    public string? CompanyStatus { get; init; }
    public string? Type { get; init; }
    public string? DateOfCreation { get; init; }
    public string? DateOfCessation { get; init; }
    public CompaniesHouseAddress? RegisteredOfficeAddress { get; init; }
    public List<string>? SicCodes { get; init; }
    public CompaniesHouseAccounts? Accounts { get; init; }
}

public sealed record CompaniesHouseAccounts
{
    public CompaniesHouseAccountingReferenceDate? AccountingReferenceDate { get; init; }
    public CompaniesHouseLastAccounts? LastAccounts { get; init; }
}

public sealed record CompaniesHouseAccountingReferenceDate
{
    public string? Day { get; init; }
    public string? Month { get; init; }
}

public sealed record CompaniesHouseLastAccounts
{
    public string? MadeUpTo { get; init; }
    public string? Type { get; init; }  // e.g., "micro-entity", "small", "medium", "dormant", etc.
}

public sealed record CompaniesHouseOfficersResponse
{
    public int TotalResults { get; init; }
    public List<CompaniesHouseOfficer> Items { get; init; } = [];
}

public sealed record CompaniesHouseOfficer
{
    public required string Name { get; init; }
    public string? OfficerRole { get; init; }
    public string? AppointedOn { get; init; }
    public string? ResignedOn { get; init; }
}

public sealed record CompaniesHouseAddress
{
    public string? Premises { get; init; }
    public string? AddressLine1 { get; init; }
    public string? AddressLine2 { get; init; }
    public string? Locality { get; init; }
    public string? Region { get; init; }
    public string? PostalCode { get; init; }
    public string? Country { get; init; }
}

public class CompaniesHouseRateLimitException : Exception
{
    public CompaniesHouseRateLimitException(string message) : base(message) { }
    public CompaniesHouseRateLimitException(string message, Exception innerException) : base(message, innerException) { }
}
