"""
User 모듈 SQLAlchemy 모델 정의

카카오 소셜 로그인 기반 사용자 관리 모델입니다.
"""

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

from sqlalchemy import BigInteger, Boolean, Date, DateTime, ForeignKey, Index, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.database.session import Base

if TYPE_CHECKING:
    from app.home.models import UserProject


class User(Base):
    """
    사용자 테이블 (카카오 소셜 로그인)

    카카오 로그인을 통해 인증된 사용자 정보를 저장합니다.

    Attributes:
        id: 고유 식별자 (자동 증가)
        kakao_id: 카카오 고유 ID (필수, 유니크)
        email: 이메일 주소 (선택, 카카오에서 제공 시)
        nickname: 카카오 닉네임 (선택)
        profile_image_url: 카카오 프로필 이미지 URL (선택)
        thumbnail_image_url: 카카오 썸네일 이미지 URL (선택)
        phone: 전화번호 (선택)
        name: 실명 (선택)
        birth_date: 생년월일 (선택)
        gender: 성별 (선택)
        is_active: 계정 활성화 상태 (기본 True)
        is_admin: 관리자 여부 (기본 False)
        role: 사용자 권한 (user, manager, admin 등)
        is_deleted: 소프트 삭제 여부 (기본 False)
        deleted_at: 삭제 일시
        last_login_at: 마지막 로그인 일시
        created_at: 계정 생성 일시
        updated_at: 계정 정보 수정 일시

    권한 체계:
        - user: 일반 사용자 (기본값)
        - manager: 매니저 (일부 관리 권한)
        - admin: 관리자 (is_admin=True와 동일)

    소프트 삭제:
        - is_deleted=True로 설정 시 삭제된 것으로 처리
        - 실제 데이터는 DB에 유지됨

    Relationships:
        user_projects: Project와의 M:N 관계 (중계 테이블 통한 연결)
        projects: 사용자가 참여한 프로젝트 목록 (Association Proxy)

    카카오 API 응답 필드 매핑:
        - kakao_id: id (카카오 회원번호)
        - email: kakao_account.email
        - nickname: kakao_account.profile.nickname 또는 properties.nickname
        - profile_image_url: kakao_account.profile.profile_image_url
        - thumbnail_image_url: kakao_account.profile.thumbnail_image_url
        - birth_date: kakao_account.birthday 또는 kakao_account.birthyear
        - gender: kakao_account.gender (male/female)
    """

    __tablename__ = "user"
    __table_args__ = (
        Index("idx_user_kakao_id", "kakao_id", unique=True),
        Index("idx_user_email", "email"),
        Index("idx_user_phone", "phone"),
        Index("idx_user_is_active", "is_active"),
        Index("idx_user_is_deleted", "is_deleted"),
        Index("idx_user_role", "role"),
        Index("idx_user_created_at", "created_at"),
        {
            "mysql_engine": "InnoDB",
            "mysql_charset": "utf8mb4",
            "mysql_collate": "utf8mb4_unicode_ci",
        },
    )

    # ==========================================================================
    # 기본 식별자
    # ==========================================================================
    id: Mapped[int] = mapped_column(
        BigInteger,
        primary_key=True,
        nullable=False,
        autoincrement=True,
        comment="고유 식별자",
    )

    # ==========================================================================
    # 카카오 소셜 로그인 필수 정보
    # ==========================================================================
    kakao_id: Mapped[int] = mapped_column(
        BigInteger,
        nullable=False,
        unique=True,
        comment="카카오 고유 ID (회원번호)",
    )

    # ==========================================================================
    # 카카오에서 제공하는 사용자 정보 (선택적)
    # ==========================================================================
    email: Mapped[Optional[str]] = mapped_column(
        String(255),
        nullable=True,
        comment="이메일 주소 (카카오 계정 이메일, 동의 시 제공)",
    )

    nickname: Mapped[Optional[str]] = mapped_column(
        String(100),
        nullable=True,
        comment="카카오 닉네임",
    )

    profile_image_url: Mapped[Optional[str]] = mapped_column(
        String(2048),
        nullable=True,
        comment="카카오 프로필 이미지 URL",
    )

    thumbnail_image_url: Mapped[Optional[str]] = mapped_column(
        String(2048),
        nullable=True,
        comment="카카오 썸네일 이미지 URL",
    )

    # ==========================================================================
    # 추가 사용자 정보
    # ==========================================================================
    phone: Mapped[Optional[str]] = mapped_column(
        String(20),
        nullable=True,
        comment="전화번호 (본인인증, 알림용)",
    )

    name: Mapped[Optional[str]] = mapped_column(
        String(50),
        nullable=True,
        comment="실명 (법적 실명, 결제/계약 시 사용)",
    )

    birth_date: Mapped[Optional[date]] = mapped_column(
        Date,
        nullable=True,
        comment="생년월일 (카카오 제공 또는 직접 입력)",
    )

    gender: Mapped[Optional[str]] = mapped_column(
        String(10),
        nullable=True,
        comment="성별 (male: 남성, female: 여성)",
    )

    # ==========================================================================
    # 계정 상태 관리
    # ==========================================================================
    is_active: Mapped[bool] = mapped_column(
        Boolean,
        nullable=False,
        default=True,
        comment="계정 활성화 상태 (비활성화 시 로그인 차단)",
    )

    is_admin: Mapped[bool] = mapped_column(
        Boolean,
        nullable=False,
        default=False,
        comment="관리자 권한 여부",
    )

    role: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        default="user",
        comment="사용자 권한 (user: 일반, manager: 매니저, admin: 관리자)",
    )

    # ==========================================================================
    # 소프트 삭제
    # ==========================================================================
    is_deleted: Mapped[bool] = mapped_column(
        Boolean,
        nullable=False,
        default=False,
        comment="소프트 삭제 여부 (True: 삭제됨)",
    )

    deleted_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime,
        nullable=True,
        comment="삭제 일시",
    )

    # ==========================================================================
    # 시간 정보
    # ==========================================================================
    last_login_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime,
        nullable=True,
        comment="마지막 로그인 일시",
    )

    created_at: Mapped[datetime] = mapped_column(
        DateTime,
        nullable=False,
        server_default=func.now(),
        comment="계정 생성 일시",
    )

    updated_at: Mapped[datetime] = mapped_column(
        DateTime,
        nullable=False,
        server_default=func.now(),
        onupdate=func.now(),
        comment="계정 정보 수정 일시",
    )

    # ==========================================================================
    # Project M:N 관계 (중계 테이블 UserProject 통한 연결)
    # ==========================================================================
    # back_populates: UserProject.user와 양방향 연결
    # cascade: User 삭제 시 UserProject 레코드도 삭제 (Project는 유지)
    # lazy="selectin": N+1 문제 방지
    # ==========================================================================
    user_projects: Mapped[List["UserProject"]] = relationship(
        "UserProject",
        back_populates="user",
        cascade="all, delete-orphan",
        lazy="selectin",
    )

    # ==========================================================================
    # RefreshToken 1:N 관계
    # ==========================================================================
    # 한 사용자는 여러 리프레시 토큰을 가질 수 있음 (다중 기기 로그인)
    # ==========================================================================
    refresh_tokens: Mapped[List["RefreshToken"]] = relationship(
        "RefreshToken",
        back_populates="user",
        cascade="all, delete-orphan",
        lazy="selectin",
    )

    def __repr__(self) -> str:
        return (
            f"<User("
            f"id={self.id}, "
            f"kakao_id={self.kakao_id}, "
            f"nickname='{self.nickname}', "
            f"role='{self.role}', "
            f"is_active={self.is_active}, "
            f"is_deleted={self.is_deleted}"
            f")>"
        )


class RefreshToken(Base):
    """
    리프레시 토큰 테이블

    JWT 리프레시 토큰을 DB에 저장하여 관리합니다.
    토큰 폐기(로그아웃), 다중 기기 로그인 관리 등에 활용됩니다.

    Attributes:
        id: 고유 식별자 (자동 증가)
        user_id: 사용자 외래키 (User.id 참조)
        token_hash: 리프레시 토큰의 SHA-256 해시값 (원본 저장 X)
        expires_at: 토큰 만료 일시
        is_revoked: 토큰 폐기 여부 (로그아웃 시 True)
        created_at: 토큰 생성 일시
        revoked_at: 토큰 폐기 일시
        user_agent: 접속 기기 정보 (브라우저, OS 등)
        ip_address: 접속 IP 주소

    보안 고려사항:
        - 토큰 원본은 저장하지 않고 해시값만 저장
        - 토큰 검증 시 해시값 비교로 유효성 확인
        - 로그아웃 시 is_revoked=True로 설정하여 재사용 방지
    """

    __tablename__ = "refresh_token"
    __table_args__ = (
        Index("idx_refresh_token_user_id", "user_id"),
        Index("idx_refresh_token_token_hash", "token_hash", unique=True),
        Index("idx_refresh_token_expires_at", "expires_at"),
        Index("idx_refresh_token_is_revoked", "is_revoked"),
        {
            "mysql_engine": "InnoDB",
            "mysql_charset": "utf8mb4",
            "mysql_collate": "utf8mb4_unicode_ci",
        },
    )

    id: Mapped[int] = mapped_column(
        Integer,
        primary_key=True,
        nullable=False,
        autoincrement=True,
        comment="고유 식별자",
    )

    user_id: Mapped[int] = mapped_column(
        BigInteger,
        ForeignKey("user.id", ondelete="CASCADE"),
        nullable=False,
        comment="사용자 외래키 (User.id 참조)",
    )

    token_hash: Mapped[str] = mapped_column(
        String(64),
        nullable=False,
        unique=True,
        comment="리프레시 토큰 SHA-256 해시값",
    )

    expires_at: Mapped[datetime] = mapped_column(
        DateTime,
        nullable=False,
        comment="토큰 만료 일시",
    )

    is_revoked: Mapped[bool] = mapped_column(
        Boolean,
        nullable=False,
        default=False,
        comment="토큰 폐기 여부 (True: 폐기됨)",
    )

    created_at: Mapped[datetime] = mapped_column(
        DateTime,
        nullable=False,
        server_default=func.now(),
        comment="토큰 생성 일시",
    )

    revoked_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime,
        nullable=True,
        comment="토큰 폐기 일시",
    )

    user_agent: Mapped[Optional[str]] = mapped_column(
        String(500),
        nullable=True,
        comment="접속 기기 정보 (브라우저, OS 등)",
    )

    ip_address: Mapped[Optional[str]] = mapped_column(
        String(45),
        nullable=True,
        comment="접속 IP 주소 (IPv4/IPv6)",
    )

    # ==========================================================================
    # User 관계
    # ==========================================================================
    user: Mapped["User"] = relationship(
        "User",
        back_populates="refresh_tokens",
    )

    def __repr__(self) -> str:
        return (
            f"<RefreshToken("
            f"id={self.id}, "
            f"user_id={self.user_id}, "
            f"is_revoked={self.is_revoked}, "
            f"expires_at={self.expires_at}"
            f")>"
        )
