from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse

from pydantic import BaseModel, Field, validator


def parse_timestamp(v: Optional[Any]) -> Optional[datetime]:
    if v is None:
        return None

    if isinstance(v, datetime):
        return v

    if isinstance(v, str):
        return datetime.fromisoformat(v)

    raise ValueError(f"Invalid timestamp: {v}")


class DomainRecord(BaseModel):
    """
    Domain record.
    """

    filename: str
    url: str | None
    offset: int
    length: int
    digest: str | None = None
    encoding: str | None = None
    timestamp: Optional[datetime] = Field(None)

    @validator("timestamp", pre=True)
    def parse_timestamp(cls, v: Optional[str]) -> Optional[datetime]:
        return parse_timestamp(v)


@dataclass
class PipeMetadata:
    """
    Metadata for a pipe.

    Attributes:
    domain_record: DomainRecord
        An instance of the DomainRecord class representing associated domain record,
        eg. pointer to the WARC file.

    article_data: Dict[Any, Any] = field(default_factory=dict)
        A dictionary storing article data with keys and values of any type.
        Those are the data extracted using Extractors.

    warc_header: Dict[str, Any] = field(default_factory=dict)
        A dictionary storing the WARC header metadata.

    http_header: Dict[str, Any] = field(default_factory=dict)
        A dictionary storing the HTTP header information.

    rec_type: str | None = None
        A string or None representing the type of record.

    encoding: str = "latin-1"
        A string representing the character encoding used for the record. The default value is "latin-1".

    name: str | None = None
        A string or None representing the name associated with the record.
    """

    domain_record: DomainRecord
    article_data: Dict[Any, Any] = field(default_factory=dict)
    warc_header: Dict[str, Any] = field(default_factory=dict)
    http_header: Dict[str, Any] = field(default_factory=dict)
    rec_type: str | None = None
    encoding: str = "latin-1"
    name: str | None = None

    def __post_init__(self):
        self.url_parsed = urlparse(self.domain_record.url)


@dataclass
class RetrieveResponse:
    """
    Response from retrieve.
    """

    content: Any


@dataclass
class DomainCrawl:
    """
    Domain crawl.
    """

    url: str = ""
    cdx_server: str = ""
    page: int = 0


# ===============================================================================
# Extractor config


class ExtractorConfig(BaseModel):
    """
    Configuration for extractor.
    """

    name: str
    since: Optional[datetime] = Field(None)
    to: Optional[datetime] = Field(None)

    @validator("since", "to", pre=True)
    def parse_timestamp(cls, v: Optional[str]) -> Optional[datetime]:
        return parse_timestamp(v)


class RoutesConfig(BaseModel):
    """
    Configuration for extractors.
    """

    regexes: List[str] = []
    extractors: List[ExtractorConfig] = []


class ExtractConfig(BaseModel):
    """
    Configuration for run.
    """

    extractors_path: Path
    routes: List[RoutesConfig]


class MatchType(Enum):
    """
    Match type for cdx server.
    See https://github.com/internetarchive/wayback/blob/master/wayback-cdx-server/README.md#url-match-scope

    Example:
    Query: example.com/abc

    Matches:
    EXACT: (www.)?example.com/abc
    PREFIX: (www.)?example.com/abc(/.*)?
    HOST: (www.)?example.com(/.*)?
    DOMAIN: (.*.)?example.com(/.*)?
    """

    EXACT = "exact"
    PREFIX = "prefix"
    HOST = "host"
    DOMAIN = "domain"

    def __str__(self):
        return self.value
