"""
验证码服务 + 绑定流程单元测试
"""

from datetime import datetime, timedelta, timezone

import pytest

from packages.adapters.in_memory.user_repository import InMemoryUserRepository
from packages.application.auth.bind_contact_use_case import (
    BindContactRequest,
    BindContactUseCase,
    SendVerificationCodeRequest,
    SendVerificationCodeUseCase,
)
from packages.application.auth.verification_code_service import (
    CODE_TYPE_EMAIL_BIND,
    CODE_TYPE_PHONE_BIND,
    VerificationCodeService,
    normalize_phone,
    validate_email,
    validate_phone,
)
from packages.domain.entities import User
from packages.domain.verification_code import VerificationCode


class InMemoryVerificationCodeRepository:
    """内存版验证码仓储"""

    def __init__(self):
        self._codes = {}

    def save(self, code):
        self._codes[code.id] = code

    def find_latest(self, recipient, code_type):
        candidates = [c for c in self._codes.values() if c.recipient == recipient and c.code_type == code_type]
        if not candidates:
            return None
        return max(candidates, key=lambda c: c.created_at)

    def find_by_id(self, code_id):
        return self._codes.get(code_id)

    def count_today(self, recipient, code_type):
        now = datetime.now(timezone.utc)
        start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
        return sum(
            1
            for c in self._codes.values()
            if c.recipient == recipient and c.code_type == code_type and c.created_at >= start_of_day
        )


@pytest.fixture
def vc_repo():
    return InMemoryVerificationCodeRepository()


@pytest.fixture
def vc_service(vc_repo):
    return VerificationCodeService(repo=vc_repo)


@pytest.fixture
def user_repo():
    repo = InMemoryUserRepository()
    # 创建一个微信用户（未绑定手机+邮箱）
    user = User(
        id="user_001",
        email="wx_test@wechat.local",
        username="wx_test",
        display_name="微信测试用户",
        password_hash="hash",
        email_verified=True,
        wechat_openid="openid_test",
    )
    repo.save(user)
    return repo


class TestPhoneValidation:
    def test_valid_phone(self):
        ok, _ = validate_phone("13800138000")
        assert ok

    def test_valid_phone_with_86(self):
        ok, _ = validate_phone("+8613800138000")
        assert ok

    def test_invalid_phone_too_short(self):
        ok, _ = validate_phone("12345")
        assert not ok

    def test_invalid_phone_wrong_prefix(self):
        ok, _ = validate_phone("12000000000")
        assert not ok

    def test_normalize_phone_strip_86(self):
        assert normalize_phone("+8613800138000") == "13800138000"
        assert normalize_phone("13800138000") == "13800138000"


class TestEmailValidation:
    def test_valid_email(self):
        ok, _ = validate_email("test@example.com")
        assert ok

    def test_invalid_email_no_at(self):
        ok, _ = validate_email("testexample.com")
        assert not ok

    def test_invalid_email_empty(self):
        ok, _ = validate_email("")
        assert not ok


class TestVerificationCodeService:
    def test_generate_code(self, vc_service):
        code, err = vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND)
        assert err is None
        assert code is not None
        assert len(code.code) == 6
        assert code.code.isdigit()
        assert code.recipient == "13800138000"
        assert code.code_type == CODE_TYPE_PHONE_BIND
        assert code.is_valid

    def test_generate_code_invalid_type(self, vc_service):
        code, err = vc_service.generate("13800138000", "invalid_type")
        assert err is not None
        assert code is None

    def test_verify_success(self, vc_service):
        code, _ = vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND)
        ok, err = vc_service.verify("13800138000", CODE_TYPE_PHONE_BIND, code.code)
        assert ok
        assert err is None
        assert code.is_used

    def test_verify_wrong_code(self, vc_service):
        vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND)
        ok, err = vc_service.verify("13800138000", CODE_TYPE_PHONE_BIND, "000000")
        assert not ok
        assert "错误" in err

    def test_verify_expired_code(self, vc_service, vc_repo):
        # 手动创建一个已过期的验证码
        expired = VerificationCode.create("13800138000", CODE_TYPE_PHONE_BIND, ttl_seconds=1)
        expired.created_at = datetime.now(timezone.utc) - timedelta(seconds=10)
        expired.expires_at = datetime.now(timezone.utc) - timedelta(seconds=5)
        vc_repo.save(expired)

        ok, err = vc_service.verify("13800138000", CODE_TYPE_PHONE_BIND, expired.code)
        assert not ok
        assert "过期" in err

    def test_resend_cooldown(self, vc_service):
        vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND)
        _, err = vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND)
        assert err is not None
        assert "频繁" in err


class TestBindContactUseCase:
    def test_bind_phone_and_email(self, vc_service, vc_repo, user_repo):
        # 直接生成已知验证码（绕开发送流程，测试绑定逻辑）
        phone_code, _ = vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND, custom_code="123456")
        email_code, _ = vc_service.generate("test@example.com", CODE_TYPE_EMAIL_BIND, custom_code="654321")
        assert phone_code and email_code

        # 4. 绑定
        bind_uc = BindContactUseCase(
            user_repository=user_repo,
            verification_code_service=vc_service,
        )
        bind_req = BindContactRequest(
            user_id="user_001",
            phone="13800138000",
            phone_code="123456",
            email="test@example.com",
            email_code="654321",
        )
        resp, err = bind_uc.execute(bind_req)
        assert err is None
        assert resp is not None
        assert resp.user.phone == "13800138000"
        assert resp.user.phone_verified
        assert resp.user.email == "test@example.com"
        assert resp.user.email_verified
        assert resp.user.binding_completed_at is not None

    def test_bind_phone_only(self, vc_service, vc_repo, user_repo):
        vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND, custom_code="123456")

        bind_uc = BindContactUseCase(
            user_repository=user_repo,
            verification_code_service=vc_service,
        )
        bind_req = BindContactRequest(
            user_id="user_001",
            phone="13800138000",
            phone_code="123456",
        )
        resp, err = bind_uc.execute(bind_req)
        assert err is None
        assert resp.user.phone == "13800138000"
        assert resp.user.phone_verified
        # 只绑了手机，邮箱还是 wechat.local，所以 binding_complete 为 false
        assert resp.user.binding_completed_at is None

    def test_bind_phone_wrong_code(self, vc_service, user_repo):
        vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND)

        bind_uc = BindContactUseCase(
            user_repository=user_repo,
            verification_code_service=vc_service,
        )
        bind_req = BindContactRequest(
            user_id="user_001",
            phone="13800138000",
            phone_code="000000",
        )
        resp, err = bind_uc.execute(bind_req)
        assert err is not None
        assert resp is None

    def test_bind_phone_already_used(self, vc_service, user_repo):
        # 另一个用户已经绑定了这个手机号
        other = User(
            id="user_002",
            email="other@example.com",
            username="other",
            display_name="其他用户",
            password_hash="hash",
            phone="13800138000",
            phone_verified=True,
        )
        user_repo.save(other)

        vc_service.generate("13800138000", CODE_TYPE_PHONE_BIND, custom_code="123456")

        bind_uc = BindContactUseCase(
            user_repository=user_repo,
            verification_code_service=vc_service,
        )
        bind_req = BindContactRequest(
            user_id="user_001",
            phone="13800138000",
            phone_code="123456",
        )
        resp, err = bind_uc.execute(bind_req)
        assert err is not None
        assert "已被其他账号绑定" in err


class TestWechatOAuthService:
    def test_mock_mode_generate_url(self):
        # 没配置真实微信参数时走 mock 模式
        from packages.application.auth.wechat_oauth_service import WechatOAuthService

        service = WechatOAuthService(app_id="", app_secret="", redirect_uri="")
        assert not service.is_configured()

        url, state = service.generate_auth_url()
        assert state
        assert "mock" in url

    def test_mock_mode_callback(self):
        from packages.application.auth.wechat_oauth_service import WechatOAuthService

        service = WechatOAuthService(app_id="", app_secret="", redirect_uri="")
        # 先生成授权 URL 获得有效 state（state 会被存入 store）
        _, valid_state = service.generate_auth_url()
        user_info, err = service.handle_callback("test_code", valid_state)
        assert err is None
        assert user_info is not None
        assert "mock" in user_info.openid
        assert user_info.nickname == "微信测试用户"

    def test_callback_invalid_state_rejected(self):
        """无效 state 应被拒绝（CSRF 防护）"""
        from packages.application.auth.wechat_oauth_service import WechatOAuthService

        service = WechatOAuthService(app_id="", app_secret="", redirect_uri="")
        # 直接用随机 state 调用，未经过 generate_auth_url
        user_info, err = service.handle_callback("test_code", "random_fake_state")
        assert err is not None
        assert "state" in err
        assert user_info is None

    def test_callback_state_single_use(self):
        """state 只能使用一次（防重放）"""
        from packages.application.auth.wechat_oauth_service import WechatOAuthService

        service = WechatOAuthService(app_id="", app_secret="", redirect_uri="")
        _, valid_state = service.generate_auth_url()

        # 第一次使用：成功
        user_info, err = service.handle_callback("test_code", valid_state)
        assert err is None
        assert user_info is not None

        # 第二次使用相同 state：失败（已被消费）
        user_info2, err2 = service.handle_callback("test_code", valid_state)
        assert err2 is not None
        assert "state" in err2
        assert user_info2 is None

    def test_callback_empty_state_rejected(self):
        """空 state 应被拒绝"""
        from packages.application.auth.wechat_oauth_service import WechatOAuthService

        service = WechatOAuthService(app_id="", app_secret="", redirect_uri="")
        user_info, err = service.handle_callback("test_code", "")
        assert err is not None
        assert "state" in err
        assert user_info is None
