using Azure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using OnlineAssessment.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;

public class OdooService
{
    protected readonly HttpClient _httpClient;
    protected readonly OdooConfig _config;
    protected readonly IMemoryCache _cache;

    public OdooService(HttpClient httpClient, IOptions<OdooConfig> config, IMemoryCache cache)
    {
        _httpClient = httpClient;
        _config = config.Value;
        _cache = cache;
    }

    public async Task<string> AuthenticateAsync()
    {
        const string cacheKey = "OdooSession";
        if (_cache.TryGetValue(cacheKey, out string cachedSessionId))
        {
            return cachedSessionId;
        }

        var payload = new
        {
            jsonrpc = "2.0",
            @params = new
            {
                db = _config.Db,
                login = _config.Username,
                password = _config.Password
            }
        };

        var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/session/authenticate", payload);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Authentication failed with status code: {response.StatusCode}");
        }

        var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
        var sessionId = ExtractSessionId(response);

        if (string.IsNullOrEmpty(sessionId))
        {
            throw new Exception("Session ID not found in authentication response.");
        }

        CacheSessionId(sessionId);

        return sessionId;
    }

    public async Task<int> CreateAsync(string model, object fields)
    {
        var sessionId = await AuthenticateAsync();
        _httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");

        var payload = new
        {
            jsonrpc = "2.0",
            method = "call",
            @params = new
            {
                model = model,
                method = "create",
                args = new[] { fields },
                kwargs = new { }
            }
        };

        var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw/{model}/create", payload);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Failed to create record with status code: {response.StatusCode}");
        }

        var responseContent1 = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Response Content: {responseContent1}");

        var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
        if (result == null || !result.RootElement.TryGetProperty("result", out var idElement))
        {
            throw new Exception("Failed to retrieve ID from response.");
        }

        return idElement.GetInt32();
    }


    public async Task<bool> DeleteAsync(string model, int recordId)
    {
        var sessionId = await AuthenticateAsync();
        _httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");

        var payload = new
        {
            jsonrpc = "2.0",
            method = "call",
            @params = new
            {
                model = model,
                method = "unlink",  // Use the 'unlink' method to delete the record
                args = new[] { recordId },
                kwargs = new { }
            }
        };

        var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", payload);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Failed to delete record with status code: {response.StatusCode}");
        }

        var responseContent = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Response Content: {responseContent}");

        var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
        if (result == null || !result.RootElement.TryGetProperty("result", out var resultElement))
        {
            throw new Exception("Failed to retrieve result from the response.");
        }

        // If result is 1, it means the operation (deletion) was successful
        return resultElement.GetBoolean();
    }


    /// <summary>
    /// Updates an existing record in Odoo.
    /// </summary>
    /// <param name="model">The Odoo model to update.</param>
    /// <param name="recordId">The ID of the record to update.</param>
    /// <param name="updateData">The fields to update.</param>
    /// <returns>True if the update was successful, otherwise false.</returns>
    public async Task<bool> UpdateAsync(string model, int recordId, Dictionary<string, object> updateData)
    {
        if (string.IsNullOrWhiteSpace(model))
        {
            throw new ArgumentException("Model name cannot be null or empty.", nameof(model));
        }

        if (recordId <= 0)
        {
            throw new ArgumentException("Invalid record ID.", nameof(recordId));
        }

        if (updateData == null || updateData.Count == 0)
        {
            throw new ArgumentNullException(nameof(updateData), "Update data cannot be null or empty.");
        }

        var sessionId = await AuthenticateAsync();
        _httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");

        var payload = new
        {
            jsonrpc = "2.0",
            method = "call",
            @params = new
            {
                model = model,
                method = "write",
                args = new object[] { new int[] { recordId }, updateData },
                kwargs = new { }
            }
        };

        var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw/{model}/write", payload);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Failed to update record with status code: {response.StatusCode}");
        }

        var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
        if (result == null || !result.RootElement.TryGetProperty("result", out var successElement))
        {
            throw new Exception("Failed to retrieve update status from response.");
        }

        return successElement.GetBoolean();
    }


    /// <summary>
    /// Performs a search_read operation on the specified Odoo model.
    /// </summary>
    /// <param name="model">The Odoo model to search in.</param>
    /// <param name="filterData">The filter criteria and fields to retrieve.</param>
    /// <returns>A list of results from the search_read operation.</returns>
    /// <exception cref="Exception">Thrown when the request fails or response is invalid.</exception>
    public async Task<List<object>> SearchAsync(string model, object filterData, string[] fields)
    {
        if (string.IsNullOrWhiteSpace(model))
            throw new ArgumentException("Model name cannot be null or empty.", nameof(model));

        var sessionId = await AuthenticateAsync();
        _httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");

        // Extract `domain` from `filterData`
        var domainFilter = filterData.GetType().GetProperty("domain")?.GetValue(filterData) as List<object>;

        var payload = new
        {
            jsonrpc = "2.0",
            method = "call",
            @params = new
            {
                model,
                method = "search_read",
                args = new object[] { domainFilter },
                kwargs = new { fields }
            }
        };

        var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", payload);

        if (!response.IsSuccessStatusCode)
            throw new HttpRequestException($"Failed to search '{model}' with status code: {response.StatusCode}");

        var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
        if (result == null || !result.RootElement.TryGetProperty("result", out var records))
            throw new Exception($"Failed to retrieve records from search response for '{model}'.");

        return records.EnumerateArray().Select(record => JsonSerializer.Deserialize<object>(record.GetRawText())).ToList();
    }


    /// <summary>
    /// Performs a search_read operation on the specified Odoo model.
    /// </summary>
    /// <param name="model">The Odoo model to search in.</param>
    /// <param name="filterData">The filter criteria and fields to retrieve.</param>
    /// <returns>A list of results from the search_read operation.</returns>
    /// <exception cref="Exception">Thrown when the request fails or response is invalid.</exception>
    public async Task<List<object>> SearchReadAttendanceAsync(string model, object filterData)
    {
        if (string.IsNullOrWhiteSpace(model))
        {
            throw new ArgumentException("Model name cannot be null or empty.", nameof(model));
        }

        if (filterData == null)
        {
            throw new ArgumentNullException(nameof(filterData), "Filter data cannot be null.");
        }

        var sessionId = await AuthenticateAsync();
        _httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");

        // Extract `domain` from `filterData`
        var domainFilter = filterData.GetType().GetProperty("domain")?.GetValue(filterData) as List<object>;


        var payload = new
        {
            jsonrpc = "2.0",
            method = "call",
            @params = new
            {
                model,
                method = "search_read",
                args = new object[] { domainFilter },
                kwargs = new { fields = new[] { "id", "employee_id", "check_in", "check_out" } }
            }
        };

        var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", payload);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Failed to perform search_read on model '{model}' with status code: {response.StatusCode}");
        }


        var responseContent1 = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Response Content: {responseContent1}");


        var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
        if (result == null || !result.RootElement.TryGetProperty("result", out var records))
        {
            throw new Exception("Failed to retrieve results from search_read response.");
        }

        return records.EnumerateArray().Select(record => JsonSerializer.Deserialize<object>(record.GetRawText())).ToList();
    }

    public async Task<bool> ValidateSubscriptionAsync(string instituteId)
    {
        try
        {
            var sessionId = await AuthenticateAsync();
            _httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");

            var domain = new[]
            {
                new object[] { "company_registry", "=", instituteId }
            };

            var companySearchPayload = new
            {
                jsonrpc = "2.0",
                method = "call",
                @params = new
                {
                    model = "res.partner",
                    method = "search_read",
                    args = new object[] { domain },
                    kwargs = new { fields = new[] { "id" } }
                }
            };

            var companyResponse = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", companySearchPayload);

            if (!companyResponse.IsSuccessStatusCode)
            {
                return false;
            }

            var companyData = await companyResponse.Content.ReadFromJsonAsync<JsonDocument>();
            if (companyData == null || !companyData.RootElement.TryGetProperty("result", out var resultIdList))
            {
                return false;
            }

            var firstRecord = resultIdList.EnumerateArray().FirstOrDefault();

            if (!(firstRecord.ValueKind == JsonValueKind.Object) || !firstRecord.TryGetProperty("id", out var companyIdElement))
            {
                return false;
            }

            var companyId = companyIdElement.GetInt32();

            var domain2 = new[]
            {
                new object[] { "partner_id", "=", companyId }
            };

            var subscriptionSearchPayload = new
            {
                jsonrpc = "2.0",
                method = "call",
                @params = new
                {
                    model = "sale.subscription.report",
                    method = "search_read",
                    args = new object[] { domain2 },
                    kwargs = new { fields = new[] { "subscription_state" } }
                }
            };

            var subscriptionResponse = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", subscriptionSearchPayload);

            if (!subscriptionResponse.IsSuccessStatusCode)
            {
                return false;
            }

            var subscriptionData = await subscriptionResponse.Content.ReadFromJsonAsync<JsonDocument>();
            if (subscriptionData == null || !subscriptionData.RootElement.TryGetProperty("result", out var resultStateList))
            {
                return false;
            }

            firstRecord = resultStateList.EnumerateArray().FirstOrDefault();

            if (!(firstRecord.ValueKind == JsonValueKind.Object) || !firstRecord.TryGetProperty("subscription_state", out var subscriptionElement))
            {
                return false;
            }

            var subscriptionState = subscriptionElement.GetString();

            return subscriptionState == "3_progress";
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error validating subscription: {ex.Message}");
            return false;
        }
    }

    private string ExtractSessionId(HttpResponseMessage response)
    {
        if (response.Headers.TryGetValues("Set-Cookie", out var cookies))
        {
            var sessionCookie = cookies.FirstOrDefault(c => c.Contains("session_id"));
            return sessionCookie?.Split(';').FirstOrDefault()?.Split('=')[1];
        }

        return null;
    }

    private void CacheSessionId(string sessionId)
    {
        var cacheEntryOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
            Size = 1
        };

        _cache.Set("OdooSession", sessionId, cacheEntryOptions);
    }
}
