import jwt
import hashlib
from datetime import datetime, timedelta

import factory
import uuid
from faker.generator import random

from app.extensions.utils.enum.aws_enum import CloudFrontEnum
from app.extensions.utils.time_helper import (
    get_server_timestamp,
    get_str_from_today,
    get_jwt_access_expired_time_delta,
    get_jwt_access_expired_timestamp,
    get_jwt_refresh_expired_timestamp,
    get_jwt_refresh_expired_time_delta,
)
from faker import Faker

from app.persistence.model import UserModel, PlantCategoryModel
from app.persistence.model.banned_word_model import BannedWordModel
from app.persistence.model.blacklist_model import BlacklistModel
from app.persistence.model.comment_like_status_model import CommentLikeStatusModel
from app.persistence.model.comment_model import CommentModel
from app.persistence.model.device_model import DeviceModel
from app.persistence.model.jwt_model import JwtModel
from app.persistence.model.notification_token_model import NotificationTokenModel
from app.persistence.model.plant_info_model import PlantInfoModel
from app.persistence.model.plant_profile_model import PlantProfileModel
from app.persistence.model.post_attachment_model import PostAttachmentModel
from app.persistence.model.post_category_model import PostCategoryModel
from app.persistence.model.post_like_status_model import PostLikeStatusModel
from app.persistence.model.post_model import PostModel
from app.persistence.model.receive_push_type_model import ReceivePushTypeModel
from app.persistence.model.user_profile_model import UserProfileModel
from core.domains.community.enum.community_enum import LastUserActionEnum
from core.domains.oauth.enum.oauth_enum import ProviderEnum
from core.domains.plant.enum.plant_enum import PlantCategoryEnum
from core.domains.user.enum.user_enum import MobileOSEnum, UserGroupEnum

fake = Faker(locale="ko_KR")


def make_custom_jwt(
    obj: any,
    token_type: str | None = None,
    now: datetime | None = None,
    delta: timedelta | None = None,
) -> bytes:
    uid = str(uuid.uuid4())
    if not now:
        now = datetime.utcnow()
    if not delta:
        delta = get_jwt_access_expired_time_delta()
    payload = {
        # additional info
        "identity": str(obj),
        "fresh": False,
        "type": token_type,
        # basic info
        "iat": now,
        "nbf": now,
        "jti": uid,
        "exp": now + delta,
    }

    custom_secret_key = "hawaii"
    encrypt_algorithm = "HS256"
    return jwt.encode(
        payload=payload, key=custom_secret_key, algorithm=encrypt_algorithm
    )


class BaseFactory(factory.Factory):
    class Meta(object):
        abstract = True


class UserFactory(BaseFactory):
    """
    Define user factory
    """

    class Meta:
        model = UserModel

    id = factory.Sequence(lambda n: n + 1)
    email = "test@plantra.io"
    join_date = get_str_from_today()
    provider = random.choice(ProviderEnum.list())
    provider_id = f"provider_id {factory.Sequence(lambda n: n + 1)}"
    group = UserGroupEnum.USER.value
    nickname = fake.name()
    is_out = False
    is_available = True
    current_connection_time = get_server_timestamp()
    uuid = str(uuid.uuid4())
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()

    @factory.post_generation
    def device(obj, create, extracted, **kwargs):
        if extracted:
            DeviceFactory(users=obj, **kwargs)

    @factory.post_generation
    def notification_token(obj, create, extracted, **kwargs):
        if extracted:
            NotificationTokenFactory(users=obj, **kwargs)

    @factory.post_generation
    def receive_push_type(obj, create, extracted, **kwargs):
        if extracted:
            ReceivePushTypeFactory(users=obj, **kwargs)

    @factory.post_generation
    def user_profile(obj, create, extracted, **kwargs):
        if extracted:
            UserProfileFactory(users=obj, **kwargs)

    @factory.post_generation
    def jwts(obj, create, extracted, **kwargs):
        if extracted:
            JwtFactory(users=obj, **kwargs)

    @factory.post_generation
    def blacklists(obj, create, extracted, **kwargs):
        if extracted:
            BlacklistFactory(users=obj, **kwargs)

    @factory.post_generation
    def plant_profile(obj, create, extracted, **kwargs):
        if extracted:
            PlantProfileFactory(users=obj, **kwargs)

    @factory.post_generation
    def posts(obj, create, extracted, **kwargs):
        if extracted:
            PostFactory(users=obj, **kwargs)


class NotificationTokenFactory(BaseFactory):
    class Meta:
        model = NotificationTokenModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    token = str(uuid.uuid4())
    endpoint = f"https://www.plantra.io/{str(uuid.uuid4())}"
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class DeviceFactory(BaseFactory):
    class Meta:
        model = DeviceModel

    id = factory.Sequence(lambda n: n + 1)
    # user_id = factory.lazy_attribute(lambda a: UserFactory())
    user_id = UserFactory.id
    mobile_os = random.choice(MobileOSEnum.list())
    hw_mac = str(uuid.uuid4())
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class ReceivePushTypeFactory(BaseFactory):
    class Meta:
        model = ReceivePushTypeModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    is_comment = True
    is_like = True
    is_incubator = True
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class UserProfileFactory(BaseFactory):
    class Meta:
        model = UserProfileModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    uuid = str(uuid.uuid4())
    file_name = fake.file_name()
    extension = fake.file_extension()
    path = f"{CloudFrontEnum.PLANTRA_CLOUD_FRONT_DOMAIN.value}/{uuid}.{extension}"
    is_available = True
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class JwtFactory(BaseFactory):
    class Meta:
        model = JwtModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    access_token = make_custom_jwt(
        UserFactory.id,
        token_type="access",
        now=get_server_timestamp(),
        delta=get_jwt_access_expired_time_delta(),
    )
    refresh_token = make_custom_jwt(
        UserFactory.id,
        token_type="refresh",
        now=get_server_timestamp(),
        delta=get_jwt_refresh_expired_time_delta(),
    )
    access_expired_at = get_jwt_access_expired_timestamp()
    refresh_expired_at = get_jwt_refresh_expired_timestamp()


class BlacklistFactory(BaseFactory):
    class Meta:
        model = BlacklistModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    access_token = make_custom_jwt(
        UserFactory.id,
        token_type="access",
        now=get_server_timestamp(),
        delta=get_jwt_access_expired_time_delta(),
    )


class PlantProfileFactory(BaseFactory):
    class Meta:
        model = PlantProfileModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    name = fake.name()
    start_growing_date = get_str_from_today()
    plant_category_id = 1
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class PlantCategoryFactory(BaseFactory):
    class Meta:
        model = PlantCategoryModel

    id = factory.Sequence(lambda n: n + 1)
    name = PlantCategoryEnum.HOUSE_PLANT.value
    is_available = True
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class PlantInfoFactory(BaseFactory):
    class Meta:
        model = PlantInfoModel

    id = factory.Sequence(lambda n: n + 1)
    plant_id = PlantProfileFactory.id
    interrupt = "PUSH"
    temperature = fake.pyfloat(positive=True, right_digits=1, left_digits=3)
    indoor_humidity = fake.pyfloat(positive=True, right_digits=1, left_digits=3)
    soil_humidity = fake.pyfloat(positive=True, right_digits=1, left_digits=3)
    illuminance = fake.pyfloat(positive=True, right_digits=1, left_digits=3)
    created_date = "20220701"
    created_time = "0800"
    created_at = get_server_timestamp()


class PostFactory(BaseFactory):
    class Meta:
        model = PostModel

    id = factory.Sequence(lambda n: n + 1)
    user_id = UserFactory.id
    title = fake.sentence()
    body = fake.sentence()
    is_deleted = False
    like_count = 0
    post_category_id = 1
    last_user_action = LastUserActionEnum.CREATE.value
    last_user_action_at = get_server_timestamp()
    last_admin_action = None
    last_admin_action_at = None
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()

    @factory.post_generation
    def comments(obj, create, extracted, **kwargs):
        if extracted:
            CommentFactory(posts=obj, **kwargs)

    @factory.post_generation
    def post_like_statuses(obj, create, extracted, **kwargs):
        if extracted:
            PostLikeStatusFactory(posts=obj, **kwargs)

    @factory.post_generation
    def post_attachments(obj, create, extracted, **kwargs):
        if extracted:
            for _ in range(0, 2):
                PostAttachmentFactory(posts=obj, **kwargs)


class PostLikeStatusFactory(BaseFactory):
    class Meta:
        model = PostLikeStatusModel

    id = factory.Sequence(lambda n: n + 1)
    post_id = PostFactory.id
    user_id = UserFactory.id
    is_liked = False
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class PostAttachmentFactory(BaseFactory):
    class Meta:
        model = PostAttachmentModel

    id = factory.Sequence(lambda n: n + 1)
    post_id = PostFactory.id
    uuid = str(uuid.uuid4())
    file_name = fake.file_name()
    path = f"https://cdn-plantra.io/{uuid}"
    extension = fake.file_extension()
    is_available = True
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class PostCategoryFactory(BaseFactory):
    class Meta:
        model = PostCategoryModel

    id = factory.Sequence(lambda n: n + 1)
    name = "관엽식물 커뮤니티"
    is_available = True
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class BannedWordFactory(BaseFactory):
    class Meta:
        model = BannedWordModel

    id = factory.Sequence(lambda n: n + 1)
    word = "시발"
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()


class CommentFactory(BaseFactory):
    class Meta:
        model = CommentModel

    id = factory.Sequence(lambda n: n + 1)
    post_id = PostFactory.id
    user_id = UserFactory.id
    body = f"댓글내용 {factory.Sequence(lambda n: n + 1)}"
    is_deleted = False
    like_count = 0
    last_user_action = LastUserActionEnum.CREATE.value
    last_user_action_at = get_server_timestamp()
    last_admin_action = None
    last_admin_action_at = None
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()

    @factory.post_generation
    def comment_like_status(obj, create, extracted, **kwargs):
        if extracted:
            CommentLikeStatusFactory(comments=obj, **kwargs)

    @factory.post_generation
    def user(obj, create, extracted, **kwargs):
        if extracted:
            UserFactory(comments=obj, **kwargs)


class CommentLikeStatusFactory(BaseFactory):
    class Meta:
        model = CommentLikeStatusModel

    id = factory.Sequence(lambda n: n + 1)
    comment_id = CommentFactory.id
    user_id = UserFactory.id
    is_liked = False
    created_at = get_server_timestamp()
    updated_at = get_server_timestamp()
