#!/usr/bin/env python
"""Small dependency-free FINATA B2B Public API client sample."""

from __future__ import annotations

import argparse
import json
import os
import re
import sys
import urllib.error
import urllib.request
from dataclasses import dataclass
from typing import Any


DEFAULT_BASE_URL = "https://finata.co.kr"
RESOURCES = ("metrics", "financials", "ownership", "governance", "activism")


class FinataApiError(RuntimeError):
    def __init__(
        self,
        message: str,
        *,
        status: int = 0,
        code: str = "",
        response: Any = None,
        quota: dict[str, str | None] | None = None,
    ) -> None:
        super().__init__(message)
        self.status = status
        self.code = code
        self.response = response
        self.quota = quota or {}


def normalize_ticker(value: str) -> str:
    text = str(value or "").strip()
    text = re.sub(r"^A", "", text, flags=re.IGNORECASE)
    text = re.sub(r"\.(KS|KQ)$", "", text, flags=re.IGNORECASE)
    digits = re.sub(r"\D", "", text)
    if not digits or len(digits) > 6:
        raise ValueError("ticker must contain one to six digits")
    return digits.zfill(6)


def normalize_base_url(value: str) -> str:
    text = str(value or DEFAULT_BASE_URL).strip().rstrip("/")
    if not re.match(r"^https?://", text, flags=re.IGNORECASE):
        raise ValueError("base_url must start with http:// or https://")
    return text


def quota_headers(headers: Any) -> dict[str, str | None]:
    return {
        "quotaCost": headers.get("x-quota-cost"),
        "quotaUsed": headers.get("x-quota-used"),
        "quotaLimit": headers.get("x-quota-limit"),
        "rateLimit": headers.get("x-ratelimit-limit"),
        "rateRemaining": headers.get("x-ratelimit-remaining"),
    }


@dataclass
class FinataClient:
    api_key: str | None = None
    base_url: str = DEFAULT_BASE_URL
    timeout: int = 15

    def __post_init__(self) -> None:
        self.api_key = self.api_key or os.getenv("FINATA_API_KEY", "")
        self.base_url = normalize_base_url(os.getenv("FINATA_BASE_URL", self.base_url))

    def request(self, path: str, *, auth: bool = True) -> dict[str, Any]:
        headers = {"Accept": "application/json"}
        if auth:
            if not self.api_key:
                raise ValueError("FINATA_API_KEY is required for data resources.")
            headers["Authorization"] = f"Bearer {self.api_key}"
        request = urllib.request.Request(f"{self.base_url}{path}", headers=headers, method="GET")
        try:
            with urllib.request.urlopen(request, timeout=self.timeout) as response:
                return self._parse_response(response.status, response.headers, response.read())
        except urllib.error.HTTPError as error:
            body = error.read()
            parsed = self._decode_json(body)
            message = parsed.get("error", {}).get("message") if isinstance(parsed, dict) else None
            code = parsed.get("error", {}).get("code") if isinstance(parsed, dict) else ""
            raise FinataApiError(
                message or f"FINATA API error: {error.code}",
                status=error.code,
                code=code or "",
                response=parsed,
                quota=quota_headers(error.headers),
            ) from error

    def _parse_response(self, status: int, headers: Any, body: bytes) -> dict[str, Any]:
        parsed = self._decode_json(body)
        if not isinstance(parsed, dict):
            raise FinataApiError(
                "FINATA API returned a non-object JSON response.",
                status=status,
                response=parsed,
                quota=quota_headers(headers),
            )
        if parsed.get("ok") is False:
            error = parsed.get("error") if isinstance(parsed.get("error"), dict) else {}
            raise FinataApiError(
                error.get("message") or f"FINATA API error: {status}",
                status=status,
                code=error.get("code") or "",
                response=parsed,
                quota=quota_headers(headers),
            )
        return parsed

    @staticmethod
    def _decode_json(body: bytes) -> Any:
        text = body.decode("utf-8")
        try:
            return json.loads(text)
        except json.JSONDecodeError as error:
            raise FinataApiError(f"FINATA API returned non-JSON response: {text[:120]}") from error

    def stock(self, ticker: str, resource: str) -> dict[str, Any]:
        if resource not in RESOURCES:
            raise ValueError(f"resource must be one of {', '.join(RESOURCES)}")
        return self.request(f"/api/v1/stocks/{normalize_ticker(ticker)}/{resource}")

    def metrics(self, ticker: str) -> dict[str, Any]:
        return self.stock(ticker, "metrics")

    def financials(self, ticker: str) -> dict[str, Any]:
        return self.stock(ticker, "financials")

    def ownership(self, ticker: str) -> dict[str, Any]:
        return self.stock(ticker, "ownership")

    def governance(self, ticker: str) -> dict[str, Any]:
        return self.stock(ticker, "governance")

    def activism(self, ticker: str) -> dict[str, Any]:
        return self.stock(ticker, "activism")

    def openapi(self) -> dict[str, Any]:
        return self.request("/api/v1/openapi", auth=False)


def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="FINATA B2B Public API client sample.")
    parser.add_argument("--base-url", default=os.getenv("FINATA_BASE_URL", DEFAULT_BASE_URL))
    parser.add_argument("--ticker", default="005930")
    parser.add_argument("--resource", default="metrics", choices=(*RESOURCES, "all"))
    parser.add_argument("--openapi", action="store_true", help="Fetch the public OpenAPI document without an API key.")
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(argv or sys.argv[1:])
    client = FinataClient(base_url=args.base_url)
    if args.openapi:
        payload: Any = client.openapi()
    elif args.resource == "all":
        payload = {resource: client.stock(args.ticker, resource) for resource in RESOURCES}
    else:
        payload = client.stock(args.ticker, args.resource)
    print(json.dumps(payload, ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
