import hashlib
import os
from datetime import date
from sqlalchemy import String, Integer, Float, Date, ForeignKey, Boolean, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base


class Product(Base):
    __tablename__ = "products"

    id: Mapped[int] = mapped_column(primary_key=True)
    code: Mapped[str] = mapped_column(String(10), unique=True, index=True)
    name: Mapped[str] = mapped_column(String(20))
    exchange: Mapped[str] = mapped_column(String(10), default="CZCE")

    contracts: Mapped[list["Contract"]] = relationship(
        back_populates="product", cascade="all, delete-orphan"
    )


class Contract(Base):
    __tablename__ = "contracts"
    __table_args__ = (UniqueConstraint("code"),)

    id: Mapped[int] = mapped_column(primary_key=True)
    product_id: Mapped[int] = mapped_column(ForeignKey("products.id"), index=True)
    code: Mapped[str] = mapped_column(String(10), unique=True, index=True)
    name: Mapped[str] = mapped_column(String(30))
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)

    product: Mapped["Product"] = relationship(back_populates="contracts")


class DailyBar(Base):
    __tablename__ = "daily_bars"
    __table_args__ = (UniqueConstraint("contract", "date"),)

    id: Mapped[int] = mapped_column(primary_key=True)
    contract: Mapped[str] = mapped_column(String(10), index=True)
    date: Mapped[date] = mapped_column(Date, index=True)
    open: Mapped[float] = mapped_column(Float)
    close: Mapped[float] = mapped_column(Float)
    high: Mapped[float] = mapped_column(Float)
    low: Mapped[float] = mapped_column(Float)
    amp_5d: Mapped[float | None] = mapped_column(Float, nullable=True)

    @property
    def diff(self) -> float:
        """最高-最低价差"""
        return self.high - self.low


class PositionRanking(Base):
    __tablename__ = "position_rankings"
    __table_args__ = (UniqueConstraint("contract_code", "institution", "data_type", "date"),)

    id: Mapped[int] = mapped_column(primary_key=True)
    contract_code: Mapped[str] = mapped_column(String(10), index=True)
    institution: Mapped[str] = mapped_column(String(30), index=True)
    data_type: Mapped[str] = mapped_column(String(10), index=True)
    date: Mapped[date] = mapped_column(Date, index=True)
    rank: Mapped[int] = mapped_column(Integer)
    value: Mapped[int] = mapped_column(Integer)
    change: Mapped[int] = mapped_column(Integer)


class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    username: Mapped[str] = mapped_column(String(50), unique=True)
    password_hash: Mapped[str] = mapped_column(String(128))
    role: Mapped[str] = mapped_column(String(20), default="user")

    @staticmethod
    def hash_password(password: str) -> str:
        salt = os.urandom(16)
        dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 100000)
        return salt.hex() + ":" + dk.hex()

    def check_password(self, password: str) -> bool:
        try:
            salt_hex, dk_hex = self.password_hash.split(":")
            salt = bytes.fromhex(salt_hex)
            dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 100000)
            return dk.hex() == dk_hex
        except (ValueError, AttributeError):
            return False


class Round(Base):
    __tablename__ = "rounds"

    id: Mapped[int] = mapped_column(primary_key=True)
    contract_code: Mapped[str] = mapped_column(String(10), index=True)
    daily_hands: Mapped[int] = mapped_column(Integer, default=1)
    status: Mapped[str] = mapped_column(String(20), default="active")
    started_at: Mapped[date] = mapped_column(Date)

    positions: Mapped[list["Position"]] = relationship(
        back_populates="round", cascade="all, delete-orphan"
    )

    @property
    def total_locks(self) -> int:
        return sum(p.locked_count for p in self.positions)


class Position(Base):
    __tablename__ = "positions"

    id: Mapped[int] = mapped_column(primary_key=True)
    round_id: Mapped[int] = mapped_column(ForeignKey("rounds.id"), index=True)
    open_price: Mapped[int] = mapped_column(Integer)
    amp_threshold: Mapped[int] = mapped_column(Integer)
    lock_price: Mapped[int] = mapped_column(Integer)
    hands: Mapped[int] = mapped_column(Integer, default=1)
    locked_count: Mapped[int] = mapped_column(Integer, default=0)
    opened_at: Mapped[date] = mapped_column(Date)

    round: Mapped["Round"] = relationship(back_populates="positions")


class Trade(Base):
    __tablename__ = "trades"

    id: Mapped[int] = mapped_column(primary_key=True)
    product_code: Mapped[str] = mapped_column(String(10))
    contract_code: Mapped[str] = mapped_column(String(10), index=True)
    direction: Mapped[str] = mapped_column(String(4))
    open_date: Mapped[date] = mapped_column(Date)
    open_price: Mapped[int] = mapped_column(Integer)
    open_fee: Mapped[int | None] = mapped_column(Integer, nullable=True, default=0)
    close_date: Mapped[date | None] = mapped_column(Date, nullable=True)
    close_price: Mapped[int | None] = mapped_column(Integer, nullable=True)
    close_fee: Mapped[int | None] = mapped_column(Integer, nullable=True, default=0)
    status: Mapped[str] = mapped_column(String(10), default="open")

    @property
    def pnl(self) -> int | None:
        if self.close_price is None:
            return None
        mul = 20  # glass futures point value
        if self.direction == "long":
            return (self.close_price - self.open_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
        else:
            return (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)