from datetime import datetime
from typing import List, TypedDict, Optional, TYPE_CHECKING

from sqlalchemy import and_
from sqlalchemy.orm import Mapped, aliased, mapped_column, relationship

from app.brm.brn import BRN
from app.extensions import db
from app.models import AbstractConfiguration

if TYPE_CHECKING:
    from app.models.bridges import BridgeConf
    from app.models.mirrors import Origin, Proxy, SmartProxy, StaticOrigin
    from app.models.onions import Eotk, Onion


class GroupDict(TypedDict):
    Id: int
    GroupName: str
    Description: str
    ActiveOriginCount: int


class Group(AbstractConfiguration):
    group_name: Mapped[str] = db.Column(db.String(80), unique=True, nullable=False)
    eotk: Mapped[bool]

    origins: Mapped[List["Origin"]] = relationship("Origin", back_populates="group")
    statics: Mapped[List["StaticOrigin"]] = relationship("StaticOrigin", back_populates="group")
    eotks: Mapped[List["Eotk"]] = relationship("Eotk", back_populates="group")
    onions: Mapped[List["Onion"]] = relationship("Onion", back_populates="group")
    smart_proxies: Mapped[List["SmartProxy"]] = relationship("SmartProxy", back_populates="group")
    pools: Mapped[List["Pool"]] = relationship("Pool", secondary="pool_group", back_populates="groups")

    @classmethod
    def csv_header(cls) -> List[str]:
        return super().csv_header() + [
            "group_name", "eotk"
        ]

    @property
    def brn(self) -> BRN:
        return BRN(
            group_id=self.id,
            product="group",
            provider="",
            resource_type="group",
            resource_id=str(self.id)
        )

    def to_dict(self) -> GroupDict:
        if not TYPE_CHECKING:
            from app.models.mirrors import Origin  # to prevent circular import

        active_origins_query = (
            db.session.query(aliased(Origin))
            .filter(and_(Origin.group_id == self.id, Origin.destroyed.is_(None)))
        )
        active_origins_count = active_origins_query.count()
        return {
            "Id": self.id,
            "GroupName": self.group_name,
            "Description": self.description,
            "ActiveOriginCount": active_origins_count,
        }


class Pool(AbstractConfiguration):
    pool_name: Mapped[str] = mapped_column(db.String, unique=True)
    api_key: Mapped[str]
    redirector_domain: Mapped[Optional[str]]

    bridgeconfs: Mapped[List["BridgeConf"]] = relationship("BridgeConf", back_populates="pool")
    proxies: Mapped[List["Proxy"]] = relationship("Proxy", back_populates="pool")
    lists: Mapped[List["MirrorList"]] = relationship("MirrorList", back_populates="pool")
    groups: Mapped[List[Group]] = relationship("Group", secondary="pool_group", back_populates="pools")

    @classmethod
    def csv_header(cls) -> List[str]:
        return super().csv_header() + [
            "pool_name"
        ]

    @property
    def brn(self) -> BRN:
        return BRN(
            group_id=0,
            product="pool",
            provider="",
            resource_type="pool",
            resource_id=str(self.pool_name)
        )


class PoolGroup(db.Model):  # type: ignore[name-defined,misc]
    pool_id = db.Column(db.Integer, db.ForeignKey("pool.id"), primary_key=True)
    group_id = db.Column(db.Integer, db.ForeignKey("group.id"), primary_key=True)


class MirrorList(AbstractConfiguration):
    pool_id = db.Column(db.Integer, db.ForeignKey("pool.id"))
    provider = db.Column(db.String(255), nullable=False)
    format = db.Column(db.String(20), nullable=False)
    encoding = db.Column(db.String(20), nullable=False)
    container = db.Column(db.String(255), nullable=False)
    branch = db.Column(db.String(255), nullable=False)
    role = db.Column(db.String(255), nullable=True)
    filename = db.Column(db.String(255), nullable=False)

    pool = db.relationship("Pool", back_populates="lists")

    providers_supported = {
        "github": "GitHub",
        "gitlab": "GitLab",
        "http_post": "HTTP POST",
        "s3": "AWS S3",
    }

    formats_supported = {
        "bc2": "Bypass Censorship v2",
        "bc3": "Bypass Censorship v3",
        "bca": "Bypass Censorship Analytics",
        "bridgelines": "Tor Bridge Lines",
        "rdr": "Redirector Data"
    }

    encodings_supported = {
        "json": "JSON (Plain)",
        "jsno": "JSON (Obfuscated)",
        "js": "JavaScript (Plain)",
        "jso": "JavaScript (Obfuscated)"
    }

    def destroy(self) -> None:
        self.destroyed = datetime.utcnow()
        self.updated = datetime.utcnow()

    def url(self) -> str:
        if self.provider == "gitlab":
            return f"https://gitlab.com/{self.container}/-/raw/{self.branch}/{self.filename}"
        if self.provider == "github":
            return f"https://raw.githubusercontent.com/{self.container}/{self.branch}/{self.filename}"
        if self.provider == "s3":
            return f"s3://{self.container}/{self.filename}"
        if self.provider == "http_post":
            return str(self.container)
        return "Unknown provider"

    @classmethod
    def csv_header(cls) -> List[str]:
        return super().csv_header() + [
            "provider", "format", "container", "branch", "filename"
        ]

    @property
    def brn(self) -> BRN:
        return BRN(
            group_id=0,
            product="list",
            provider=self.provider,
            resource_type="list",
            resource_id=str(self.id)
        )
