using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using API.MangaDownloadClients;
using API.Schema.Jobs;
using API.Schema.MangaConnectors;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using static System.IO.UnixFileMode;

namespace API.Schema;

[PrimaryKey("MangaId")]
public class Manga
{
    [MaxLength(64)]
    public string MangaId { get; init; }
    [MaxLength(64)]
    public string ConnectorId { get; init; }

    public string Name { get; internal set; }
    public string Description { get; internal set; }
    public string WebsiteUrl { get; internal set; }
    public string CoverUrl { get; internal set; }
    public string? CoverFileNameInCache { get; internal set; }
    public uint Year { get; internal set; }
    public string? OriginalLanguage { get; internal set; }
    public MangaReleaseStatus ReleaseStatus { get; internal set; }
    public string FolderName { get; private set; }
    public float IgnoreChapterBefore { get; internal set; }

    public string MangaConnectorId { get; private set; }
    [JsonIgnore]
    public MangaConnector? MangaConnector { get; private set; }
    
    public ICollection<string> AuthorIds { get; internal set; }
    [JsonIgnore]
    public ICollection<Author>? Authors { get; internal set; }
    
    public ICollection<string> TagIds { get; internal set; }
    [JsonIgnore]
    public ICollection<MangaTag>? Tags { get; internal set; }
    
    public ICollection<string> LinkIds { get; internal set; }
    [JsonIgnore]
    public ICollection<Link>? Links { get; internal set; }
    
    public ICollection<string> AltTitleIds { get; internal set; }
    [JsonIgnore]
    public ICollection<MangaAltTitle>? AltTitles { get; internal set; }

    public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl,
        string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
        float ignoreChapterBefore, MangaConnector mangaConnector, ICollection<Author> authors,
        ICollection<MangaTag> tags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles)
        : this(connectorId, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage,
            releaseStatus, ignoreChapterBefore, mangaConnector.Name)
    {
        this.AuthorIds = authors.Select(a => a.AuthorId) as ICollection<string>;
        this.Authors = authors;
        this.TagIds = tags.Select(t => t.Tag) as ICollection<string>;
        this.Tags = tags;
        this.LinkIds = links.Select(l => l.LinkId) as ICollection<string>;
        this.Links = links;
        this.AltTitleIds = altTitles.Select(t => t.AltTitleId) as ICollection<string>;
        this.AltTitles = altTitles;
    }
    
    public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl,
        string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
        float ignoreChapterBefore, string mangaConnectorId)
    {
        MangaId = TokenGen.CreateToken(typeof(Manga), mangaConnectorId, connectorId);
        ConnectorId = connectorId;
        Name = name;
        Description = description;
        WebsiteUrl = websiteUrl;
        CoverUrl = coverUrl;
        CoverFileNameInCache = coverFileNameInCache;
        Year = year;
        OriginalLanguage = originalLanguage;
        ReleaseStatus = releaseStatus;
        IgnoreChapterBefore = ignoreChapterBefore;
        MangaConnectorId = mangaConnectorId;
        FolderName = BuildFolderName(name);
    }

    public MoveFileOrFolderJob UpdateFolderName(string downloadLocation, string newName)
    {
        string oldName = this.FolderName;
        this.FolderName = newName;
        return new MoveFileOrFolderJob(Path.Join(downloadLocation, oldName), Path.Join(downloadLocation, this.FolderName));
    }

    internal void UpdateWithInfo(Manga other)
    {
        this.Name = other.Name;
        this.Year = other.Year;
        this.Description = other.Description;
        this.CoverUrl = other.CoverUrl;
        this.OriginalLanguage = other.OriginalLanguage;
        this.Authors = other.Authors;
        this.Links = other.Links;
        this.Tags = other.Tags;
        this.AltTitles = other.AltTitles;
        this.ReleaseStatus = other.ReleaseStatus;
    }

    private static string BuildFolderName(string mangaName)
    {
        return mangaName;
    }
    
    internal string? SaveCoverImageToCache(int retries = 3)
    {
        if(retries < 0)
            return null;
        
        Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))");
        //https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
        Match match = urlRex.Match(CoverUrl);
        string filename = $"{match.Groups[1].Value}-{MangaId}.{match.Groups[3].Value}";
        string saveImagePath = Path.Join(TrangaSettings.coverImageCache, filename);

        if (File.Exists(saveImagePath))
            return saveImagePath;
        
        RequestResult coverResult = new HttpDownloadClient().MakeRequest(CoverUrl, RequestType.MangaCover);
        if (coverResult.statusCode is < HttpStatusCode.Accepted or >= HttpStatusCode.Ambiguous)
            return SaveCoverImageToCache(--retries);
            
        using MemoryStream ms = new();
        coverResult.result.CopyTo(ms);
        Directory.CreateDirectory(TrangaSettings.coverImageCache);
        File.WriteAllBytes(saveImagePath, ms.ToArray());
        
        return saveImagePath;
    }
    
    public string CreatePublicationFolder()
    {
        string publicationFolder = Path.Join(TrangaSettings.downloadLocation, this.FolderName);
        if(!Directory.Exists(publicationFolder))
            Directory.CreateDirectory(publicationFolder);
        if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            File.SetUnixFileMode(publicationFolder, GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute | UserRead | UserWrite | UserExecute);
        return publicationFolder;
    }
    
    //TODO onchanges create job to update metadata files in archives, etc.
}