import csv
import io
from typing import Annotated

from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
from psycopg import Connection
from pydantic import BaseModel, Field
from src.__types.error_responses import (
    CONFLICT_DETAIL,
    VALIDATION_FAILED_DETAIL,
    ErrorCode,
    ErrorResponse,
    InvalidParameter,
)
from src.__types.errors import BulkDuplicatedError, ResourceDuplicatedError
from src.__types.index import Tenant, UserWriteAttribute
from src.logger import logger
from src.routers.csv_helpers import serialize_csv
from src.routers.management.authorizer import get_tenant
from src.routers.management.connection import get_connection
from src.usecases.user_command_usecase import UserCommandUseCase
from src.usecases.user_query_usecase import UserQueryUseCase

router = APIRouter(
    prefix="/users",
    tags=["ユーザー管理"],
)

MAX_BATCH_CREATE_USERS = 100
MAX_CSV_UPLOAD_SIZE_BYTES = 1 * 1024 * 1024  # 1MB
CSV_HEADER_FIELD_MAP: dict[str, str] = {
    "ID": "display_id",
    "名称": "display_name",
    "メールアドレス": "email",
}
CSV_HEADERS = list(CSV_HEADER_FIELD_MAP.keys())

CsvUploadFile = Annotated[
    UploadFile,
    File(
        description=f"""CSVファイル。必須ヘッダー: {", ".join(CSV_HEADERS)}。
        ファイルサイズ上限は{MAX_CSV_UPLOAD_SIZE_BYTES // (1024 * 1024)}MBで、{MAX_BATCH_CREATE_USERS}件までのユーザーを作成できます。""",
    ),
]


class BatchCreateUsersRequest(BaseModel):
    users: list[UserWriteAttribute] = Field(
        ...,
        description="作成するユーザーのリスト。最大100件まで。",
        min_length=1,
        max_length=MAX_BATCH_CREATE_USERS,
    )


# バリデーション
def _parse_csv(file: CsvUploadFile) -> BatchCreateUsersRequest:
    """CSVファイルをパースし、BatchCreateUsersRequestとしてバリデーションする。"""
    rows = _read_csv_rows(file)
    return _validate_users(rows)


def _read_csv_rows(file: UploadFile) -> list[dict[str, str]]:
    """CSVファイルを読み取り、行データのリストを返す。

    ファイルサイズ、エンコーディング、ヘッダーの検証を行う。
    """
    content = file.file.read()
    if len(content) > MAX_CSV_UPLOAD_SIZE_BYTES:
        raise HTTPException(
            status_code=status.HTTP_413_CONTENT_TOO_LARGE,
            detail=ErrorResponse(
                detail="File size exceeds the limit.",
                invalid_parameters=[InvalidParameter(name="file", code=ErrorCode.TOO_LARGE)],
            ).model_dump(),
        )

    # CP932(Shift_JIS)はほぼ全バイト列を受け入れるため、UTF-8を先に試行する
    try:
        text = content.decode("utf-8-sig")
    except UnicodeDecodeError:
        try:
            text = content.decode("cp932")
        except UnicodeDecodeError as e:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail=ErrorResponse(
                    detail=VALIDATION_FAILED_DETAIL,
                    invalid_parameters=[InvalidParameter(name="file", code=ErrorCode.INVALID_FORMAT)],
                ).model_dump(),
            ) from e

    reader = csv.DictReader(io.StringIO(text))

    if reader.fieldnames is None or list(reader.fieldnames) != CSV_HEADERS:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=ErrorResponse(
                detail=VALIDATION_FAILED_DETAIL,
                invalid_parameters=[InvalidParameter(name="file.headers", code=ErrorCode.INVALID_FORMAT)],
            ).model_dump(),
        )

    return [{field: row[header] for header, field in CSV_HEADER_FIELD_MAP.items()} for row in reader]


def _validate_users(rows: list[dict[str, str]]) -> BatchCreateUsersRequest:
    """行データをBatchCreateUsersRequestに変換し、バッチ内重複チェックを行う。"""
    request = BatchCreateUsersRequest.model_validate({"users": rows})
    duplicated_fields: list[str] = []

    seen_display_ids: dict[str, int] = {}
    seen_emails: dict[str, int] = {}
    for i, user in enumerate(request.users):
        if user.display_id in seen_display_ids:
            duplicated_fields.append(f"users[{i}].display_id")
        else:
            seen_display_ids[user.display_id] = i
        if user.email in seen_emails:
            duplicated_fields.append(f"users[{i}].email")
        else:
            seen_emails[user.email] = i

    if duplicated_fields:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=ErrorResponse(
                detail=VALIDATION_FAILED_DETAIL,
                invalid_parameters=[
                    InvalidParameter(name=field, code=ErrorCode.ALREADY_EXISTS) for field in duplicated_fields
                ],
            ).model_dump(),
        )

    return request


# ルーティング
@router.post(
    "/import/validate",
    summary="インポート前バリデーション",
    description="ユーザー取り込み用ファイルのインポート前のバリデーションを行います。",
    status_code=status.HTTP_204_NO_CONTENT,
    responses={
        status.HTTP_204_NO_CONTENT: {
            "description": "バリデーション成功",
        },
        status.HTTP_409_CONFLICT: {
            "description": "display_id または email が既存ユーザーと重複している",
            "model": ErrorResponse,
        },
        status.HTTP_413_CONTENT_TOO_LARGE: {
            "description": "ファイルサイズ超過",
            "model": ErrorResponse,
        },
        status.HTTP_422_UNPROCESSABLE_CONTENT: {
            "description": "バリデーションエラー。ヘッダーの不足やデータの不備、ファイル内での重複など。",
            "model": ErrorResponse,
        },
    },
)
def batch_validate_users(
    tenant: Annotated[Tenant, Depends(get_tenant)],
    connection: Annotated[Connection, Depends(get_connection)],
    request: Annotated[BatchCreateUsersRequest, Depends(_parse_csv)],
) -> Response:
    usecase = UserCommandUseCase.create(tenant=tenant, connection=connection)
    try:
        usecase.bulk_add_validate(users=request.users)
    except BulkDuplicatedError as e:
        logger.warning("Duplicate users detected during validation.", tenant_id=tenant.id)
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=ErrorResponse(
                detail=CONFLICT_DETAIL,
                invalid_parameters=[
                    InvalidParameter(name=field, code=ErrorCode.ALREADY_EXISTS) for field in e.duplicated_fields
                ],
            ).model_dump(),
        ) from e

    return Response(status_code=status.HTTP_204_NO_CONTENT)


@router.post(
    "/import",
    summary="インポート",
    description="ユーザー取り込み用ファイルをインポートして、複数のユーザーを新規作成します。",
    status_code=status.HTTP_201_CREATED,
    responses={
        status.HTTP_201_CREATED: {
            "description": "ユーザーの一括作成に成功",
        },
        status.HTTP_409_CONFLICT: {
            "description": "display_id または email が既存ユーザーと重複している",
            "model": ErrorResponse,
        },
        status.HTTP_413_CONTENT_TOO_LARGE: {
            "description": "ファイルサイズ超過",
            "model": ErrorResponse,
        },
        status.HTTP_422_UNPROCESSABLE_CONTENT: {
            "description": "バリデーションエラー。ヘッダーの不足やデータの不備、ファイル内での重複など。",
            "model": ErrorResponse,
        },
    },
)
def batch_create_users(
    tenant: Annotated[Tenant, Depends(get_tenant)],
    connection: Annotated[Connection, Depends(get_connection)],
    request: Annotated[BatchCreateUsersRequest, Depends(_parse_csv)],
) -> Response:
    usecase = UserCommandUseCase.create(tenant=tenant, connection=connection)

    try:
        usecase.bulk_add_validate(users=request.users)
        usecase.bulk_add(users=request.users)
    except BulkDuplicatedError as e:
        logger.warning("Duplicate users detected during import.", tenant_id=tenant.id)
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=ErrorResponse(
                detail=CONFLICT_DETAIL,
                invalid_parameters=[
                    InvalidParameter(name=field, code=ErrorCode.ALREADY_EXISTS) for field in e.duplicated_fields
                ],
            ).model_dump(),
        ) from e
    except ResourceDuplicatedError as e:
        logger.warning("Conflict detected during import.", tenant_id=tenant.id)
        invalid_parameters = []
        if e.duplicated_field:
            invalid_parameters.append(InvalidParameter(name=e.duplicated_field, code=ErrorCode.ALREADY_EXISTS))

        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=ErrorResponse(
                detail=CONFLICT_DETAIL,
                invalid_parameters=invalid_parameters,
            ).model_dump(),
        ) from e

    return Response(status_code=status.HTTP_201_CREATED)


@router.get(
    "/import/template",
    summary="インポートテンプレートダウンロード",
    description="ユーザーインポートのテンプレートファイルを生成して返します。",
    dependencies=[Depends(get_tenant)],
    responses={
        status.HTTP_200_OK: {
            "description": "テンプレートのダウンロードに成功",
            "content": {"text/csv": {}},
        },
    },
)
def get_import_template() -> Response:
    content = serialize_csv(CSV_HEADERS, [])

    response = Response(content=content, media_type="text/csv")
    response.headers["Content-Disposition"] = 'attachment; filename="user_import_template.csv"'
    return response


@router.get(
    "/export",
    summary="エクスポート",
    description="登録済みユーザーの一覧をエクスポートします。",
    responses={
        status.HTTP_200_OK: {
            "description": "ユーザーのエクスポートに成功",
            "content": {"text/csv": {}},
        },
    },
)
def export_users(
    tenant: Annotated[Tenant, Depends(get_tenant)],
    connection: Annotated[Connection, Depends(get_connection)],
) -> Response:
    usecase = UserQueryUseCase.create(tenant=tenant, connection=connection)
    users = usecase.list_all()

    content = serialize_csv(
        CSV_HEADERS,
        ({header: getattr(user, field, "") for header, field in CSV_HEADER_FIELD_MAP.items()} for user in users),
    )

    response = Response(content=content, media_type="text/csv")
    response.headers["Content-Disposition"] = 'attachment; filename="users.csv"'
    return response
