import re
from gc import garbage

import textdistance
from unidecode import unidecode
from datetime import datetime
from databases import (
    BuildIbanDescription,
    BuildIbans,
    BuildDecisionBook,
    BuildLivingSpace,
    AccountRecords,
)
from typing import Optional
from pydantic import BaseModel

from databases.sql_models.company.company import Companies
from databases.sql_models.identity.identity import People


class InsertBudgetRecord(BaseModel):
    iban: str
    bank_date: str = datetime.now().__str__()
    receive_debit: str = "debit"
    budget_type: str = "B"
    currency_value: float = 0
    balance: float = 0
    bank_reference_code: str = ""
    currency: str = "TL"
    channel_branch: str = ""
    process_name: str = ""
    process_type: str = ""
    process_comment: str = ""

    add_xcomment: Optional[str] = None
    project_no: Optional[str] = None
    company_id: Optional[str] = None
    customer_id: Optional[str] = None

    send_person_id: Optional[int] = None
    send_company_id: Optional[int] = None
    build_id: Optional[int] = None
    build_decision_book_id: Optional[int] = None
    build_parts_id: Optional[int] = None
    build_db_item_id: Optional[int] = None
    dues_type: Optional[str] = "D"
    period_time: Optional[str] = ""
    approving_accounting_record: Optional[bool] = False
    approving_accounting_person: Optional[str] = None
    accounting_receipt_date: Optional[str] = "1900-01-01 00:00:00"
    accounting_receipt_number: Optional[int] = 0


def strip_time_date(date_str):
    return datetime.strptime(date_str, "%Y-%m-%d")


def strip_date_to_valid(date_str):
    return datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")


def find_iban_in_comment(iban: str, comment: str, living_space_dict: dict = None):
    iban_results = BuildIbanDescription.filter_all(
        BuildIbanDescription.iban == iban, system=True
    ).data
    sm_dict_extended, sm_dict_digit = {}, {}
    for iban_result in iban_results or []:
        candidate_parts = comment.split(" ")
        extended_candidate_parts, digit_part = [], []
        for part in candidate_parts:
            if part.lower() not in ["no", "daire", "nolu"]:
                extended_candidate_parts.append(part)
            # if part.isdigit():
            #     digit_part.append(part)
        if extended_candidate_parts:
            if all(
                candidate_part.lower() in comment.lower()
                for candidate_part in extended_candidate_parts
            ):
                similarity_ratio = textdistance.jaro_winkler(
                    unidecode(str(iban_result.search_word)), comment
                )
                found = False
                name_list = (
                    unidecode(str(iban_result.search_word)).replace(".", " ").split(" ")
                )
                for name in name_list:
                    if len(name) > 3 and name.lower() in comment.lower():
                        found = True
                        break

                if not found:
                    similarity_ratio = 0.1
                sm_dict_extended[f"{iban_result.id}"] = similarity_ratio
    if sm_dict_extended:
        result = sorted(
            sm_dict_extended.items(), key=lambda item: item[1], reverse=True
        )[0]
        if float(result[1]) >= 0.5:
            iban_result = BuildIbanDescription.filter_one(
                BuildIbanDescription.id == int(result[0]), system=True
            ).data
            return {
                # "decision_book_project_id": iban_result.decision_book_project_id,
                # "build_parts_id": iban_result.build_parts_id,
                "company_id": iban_result.company_id,
                "customer_id": iban_result.customer_id,
                "found_from": "Name",
                "similarity": result[1],
            }
    return {
        # "decision_book_project_id": None,
        # "build_parts_id": None,
        "company_id": None,
        "customer_id": None,
        "found_from": None,
        "similarity": 0.0,
    }


def remove_spaces_from_string(remove_string: str):
    letter_list = []
    for letter in remove_string.split(" "):
        if letter_ := "".join(i for i in letter if not i == " "):
            letter_list.append(letter_)
    return " ".join(letter_list).upper()


def get_garbage_words(comment: str, search_word: str):
    garbage_words = unidecode(remove_spaces_from_string(comment))
    search_word = unidecode(remove_spaces_from_string(search_word))
    for word in search_word.split(" "):
        garbage_words = garbage_words.replace(
            remove_spaces_from_string(unidecode(word)), ""
        )
    if cleaned_from_spaces := remove_spaces_from_string(garbage_words):
        return str(unidecode(cleaned_from_spaces)).upper()
    return None


def remove_garbage_words(comment: str, garbage_word: str):
    cleaned_comment = remove_spaces_from_string(comment.replace("*", " "))
    if garbage_word:
        garbage_word = remove_spaces_from_string(garbage_word.replace("*", " "))
        for letter in garbage_word.split(" "):
            cleaned_comment = unidecode(remove_spaces_from_string(cleaned_comment))
            cleaned_comment = cleaned_comment.replace(
                remove_spaces_from_string(letter), ""
            )
    return str(remove_spaces_from_string(cleaned_comment)).upper()


def check_is_comment_is_build(comment: str):
    has_build_words = False
    candidate_parts = remove_spaces_from_string(comment.replace("*", " ")).split(" ")
    for candidate_part in candidate_parts:
        candidate_part = remove_spaces_from_string(candidate_part).replace(":", "")
        for build_word in ["no", "daire", "apt", "apartman"]:
            if unidecode(candidate_part).upper() in unidecode(build_word).upper():
                has_build_words = True
                break
    return has_build_words


def get_list_of_build_words(comment: str):
    build_words = []
    candidate_parts = remove_spaces_from_string(comment.replace("*", " "))
    for build_word in ["no", "nolu", "daire", "apt", "apartman"]:
        if unidecode(build_word).upper() in unidecode(candidate_parts).upper():
            st = unidecode(candidate_parts).upper().index(unidecode(build_word).upper())
            et = st + len(build_word)
            st = st - 5 if st > 5 else 0
            et = et + 5 if et + 5 <= len(candidate_parts) else len(candidate_parts)
            number_digit = "".join(
                letter for letter in str(candidate_parts[st:et]) if letter.isdigit()
            )
            if number_digit:
                rt_dict = {
                    "garbage": candidate_parts[st:et],
                    "number": int(number_digit) if number_digit else None,
                }
                build_words.append(rt_dict)
    return build_words


def generate_pattern(word):
    if len(word) < 1:
        raise ValueError("The word must have at least 1 character.")
    add_string, add_match = "\d{1,3}$\s?$", f"{{1, {len(word)}}}"
    adda_string = "d{1,3}$\s?\^["
    return adda_string + f"{word}]" + add_match + rf"{word}(?:e|é|ı|i|ğr)?" + add_string


def test_pattern(word, test_cases):  # Generate the pattern
    pattern = generate_pattern(word)
    for test in test_cases:  # Test the regex pattern on each input and print results
        if re.match(pattern, test, re.IGNORECASE):
            print(f"'{test}' matches the pattern.", "*" * 60)
        else:
            print(f"'{test}' does NOT match the pattern.")


def parse_comment_for_living_space(
    iban: str, comment: str, living_space_dict: dict = None
):
    comment = unidecode(comment)
    best_similarity = dict(
        company=None,
        living_space=None,
        found_from=None,
        similarity=0.0,
        garbage="",
        cleaned="",
    )
    if not iban in living_space_dict:
        return best_similarity
    for person in living_space_dict[iban]["people"]:
        person: People = person
        first_name = unidecode(person.firstname).upper()
        last_name = unidecode(person.surname).upper()
        search_word_list = [
            remove_spaces_from_string("".join([f"{first_name} {last_name}"])),
            remove_spaces_from_string("".join([f"{last_name} {first_name}"])),
        ]
        if middle_name := unidecode(person.middle_name).upper():
            search_word_list.append(
                remove_spaces_from_string(f"{first_name} {middle_name} {last_name}")
            )
            search_word_list.append(
                remove_spaces_from_string(f"{last_name} {middle_name} {first_name}")
            )

        cleaned_comment = unidecode(comment).upper()
        for search_word in search_word_list:
            garbage_words = get_garbage_words(comment, unidecode(search_word))
            if garbage_words:
                garbage_words = unidecode(garbage_words).upper()
                cleaned_comment = unidecode(
                    remove_garbage_words(comment, garbage_words)
                ).upper()
            similarity_ratio = textdistance.jaro_winkler(
                cleaned_comment, str(search_word).upper()
            )
            if len(cleaned_comment) < len(f"{first_name}{last_name}"):
                continue
            if cleaned_comment and 0.9 < similarity_ratio <= 1:
                print(
                    "cleaned comment dict",
                    dict(
                        garbage=garbage_words,
                        cleaned=cleaned_comment,
                        similarity=similarity_ratio,
                        search_word=search_word,
                        comment=comment,
                        last_similarity=float(best_similarity["similarity"]),
                    ),
                )
            if similarity_ratio > float(best_similarity["similarity"]):
                for living_space in living_space_dict[iban]["living_space"]:
                    if living_space.person_id == person.id:
                        best_similarity = {
                            "company": None,
                            "living_space": living_space,
                            "found_from": "Person Name",
                            "similarity": similarity_ratio,
                            "garbage": garbage_words,
                            "cleaned": cleaned_comment,
                        }
    return best_similarity


def parse_comment_for_build_parts(
    comment: str, max_build_part: int = 200, parse: str = "DAIRE"
):
    from regex_func import category_finder

    results, results_list = category_finder(comment), []
    print("results[parse]", results[parse])
    for result in results[parse] or []:
        if digits := "".join([letter for letter in str(result) if letter.isdigit()]):
            print("digits", digits)
            if int(digits) <= int(max_build_part):
                results_list.append(int(digits))
    return results_list or None


def parse_comment_for_company_or_individual(comment: str):
    companies_list = Companies.filter_all(
        Companies.commercial_type != "Commercial", system=True
    ).data
    comment = unidecode(comment)
    best_similarity = dict(
        company=None,
        living_space=None,
        found_from=None,
        similarity=0.0,
        garbage="",
        cleaned="",
    )
    for company in companies_list:
        search_word = unidecode(company.public_name)
        garbage_words = get_garbage_words(comment, search_word)
        cleaned_comment = remove_garbage_words(comment, garbage_words)
        similarity_ratio = textdistance.jaro_winkler(cleaned_comment, search_word)
        if similarity_ratio > float(best_similarity["similarity"]):
            best_similarity = {
                "company": company,
                "living_space": None,
                "found_from": "Customer Public Name",
                "similarity": similarity_ratio,
                "garbage": garbage_words,
                "cleaned": cleaned_comment,
            }
            # print(
            #     'cleaned_comment', cleaned_comment, '\n'
            #     'search_word', search_word, '\n'
            #     'best_similarity', best_similarity, '\n'
            #     'company name', company.public_name,  '\n'
            #     'similarity_ratio', similarity_ratio, '\n'
            #     'garbage_words', garbage_words
            # )
    return best_similarity


def parse_comment_to_split_with_star(account_record: AccountRecords):
    if "*" in account_record.process_comment:
        process_comment = str(account_record.process_comment.replace("**", "*"))
        process_comments = process_comment.split("*")
        return len(process_comments), *process_comments
    return 1, account_record.process_comment


def check_build_living_space_matches_with_build_parts(
    living_space_dict: dict, best_similarity: dict, iban: str, whole_comment: str
):
    if 0.6 < float(best_similarity["similarity"]) < 0.8:
        build_parts = living_space_dict[iban]["build_parts"]
        if best_similarity["living_space"]:
            build_parts_id = best_similarity["living_space"].build_parts_id
            parser_dict = dict(
                comment=str(whole_comment), max_build_part=len(build_parts)
            )
            print("build parts similarity", best_similarity, "parser_dict", parser_dict)
            results_list = parse_comment_for_build_parts(**parser_dict)
            print("results_list", results_list)
            if not results_list:
                return best_similarity
            for build_part in build_parts:
                print("part_no", int(build_part.part_no), " | ", results_list)
                print("build_part", int(build_part.id), int(build_parts_id))
                print("cond", int(build_part.id) == int(build_parts_id))
                print("cond2", int(build_part.part_no) in results_list)
                if (
                    int(build_part.id) == int(build_parts_id)
                    and int(build_part.part_no) in results_list
                ):
                    similarity = float(best_similarity["similarity"])
                    best_similarity["similarity"] = (1 - similarity) / 2 + similarity
                    print("similarity", best_similarity["similarity"])
                    break
    return best_similarity


def parse_comment_with_name(
    account_record: AccountRecords, living_space_dict: dict = None
):
    comments = parse_comment_to_split_with_star(account_record=account_record)
    best_similarity = {"similarity": 0.0}
    comments_list, comments_length = comments[1:], int(comments[0])
    print("comments_list", comments_list, "comments_length", comments_length)
    if (
        int(account_record.currency_value) > 0
    ):  #   Build receive money from living space people
        living_space_matches = dict(
            living_space_dict=living_space_dict,
            iban=account_record.iban,
            whole_comment=account_record.process_comment,
        )
        if comments_length == 1:
            best_similarity = parse_comment_for_living_space(
                iban=account_record.iban,
                comment=comments_list[0],
                living_space_dict=living_space_dict,
            )
            best_similarity["send_person_id"] = best_similarity.get("customer_id", None)
            living_space_matches["best_similarity"] = best_similarity
            # if 0.5 < float(best_similarity['similarity']) < 0.8
            best_similarity = check_build_living_space_matches_with_build_parts(
                **living_space_matches
            )
            return best_similarity
        for comment in comments_list:
            similarity_result = parse_comment_for_living_space(
                iban=account_record.iban,
                comment=comment,
                living_space_dict=living_space_dict,
            )
            if float(similarity_result["similarity"]) > float(
                best_similarity["similarity"]
            ):
                best_similarity = similarity_result
        living_space_matches["best_similarity"] = best_similarity
        # if 0.5 < float(best_similarity['similarity']) < 0.8:
        best_similarity = check_build_living_space_matches_with_build_parts(
            **living_space_matches
        )
        print("last best_similarity", best_similarity)
        return best_similarity
    else:  #  Build pays money for service taken from company or individual
        if not comments_length > 1:
            best_similarity = parse_comment_for_company_or_individual(
                comment=comments_list[0]
            )
            best_similarity["send_person_id"] = best_similarity.get("customer_id", None)
            return best_similarity
        for comment in comments_list:
            similarity_result = parse_comment_for_company_or_individual(comment=comment)
            if float(similarity_result["similarity"]) > float(
                best_similarity["similarity"]
            ):
                best_similarity = similarity_result
        return best_similarity


def parse_comment_with_name_iban_description(account_record: AccountRecords):
    comments = parse_comment_to_split_with_star(account_record=account_record)
    comments_list, comments_length = comments[1:], int(comments[0])
    iban_results = BuildIbanDescription.filter_all(
        BuildIbanDescription.iban == account_record.iban, system=True
    ).data
    best_similarity = dict(
        company=None,
        living_space=None,
        found_from=None,
        similarity=0.0,
        garbage="",
        cleaned="",
    )
    for comment in comments_list:
        for iban_result in iban_results:
            search_word = unidecode(iban_result.search_word)
            garbage_words = get_garbage_words(comment, search_word)
            cleaned_comment = remove_garbage_words(comment, garbage_words)
            similarity_ratio = textdistance.jaro_winkler(cleaned_comment, search_word)
            company = Companies.filter_by_one(
                system=True, id=iban_result.company_id
            ).data
            if float(similarity_ratio) > float(best_similarity["similarity"]):
                best_similarity = {
                    "company": company,
                    "living_space": None,
                    "found_from": "Customer Public Name Description",
                    "similarity": similarity_ratio,
                    "garbage": garbage_words,
                    "cleaned": cleaned_comment,
                }
    return best_similarity
    # print('account_record.process_comment', account_record.process_comment)
    # test_pattern(
    #     word=unidecode("no"),
    #     test_cases=[account_record.process_comment]
    # )
    # test_pattern(word="daire", test_cases=comments_list)

    # sm_dict_extended, sm_dict_digit = {}, {}
    # iban_results = BuildIbanDescription.filter_all(
    #     BuildIbanDescription.iban == iban, system=True
    # ).data
    # for iban_result in iban_results or []:
    #     candidate_parts = comment.split(" ")
    #     extended_candidate_parts, digit_part = [], []
    #     for part in candidate_parts:
    #         if part.lower() not in ["no", "daire", "nolu"]:
    #             extended_candidate_parts.append(part)
    #     if extended_candidate_parts:
    #         if all(
    #             candidate_part.lower() in comment.lower()
    #             for candidate_part in extended_candidate_parts
    #         ):
    #             similarity_ratio = textdistance.jaro_winkler(
    #                 unidecode(str(iban_result.search_word)), comment
    #             )
    #             found = False
    #             name_list = (
    #                 unidecode(str(iban_result.search_word)).replace(".", " ").split(" ")
    #             )
    #             for name in name_list:
    #                 if len(name) > 3 and name.lower() in comment.lower():
    #                     found = True
    #                     break
    #
    #             if not found:
    #                 similarity_ratio = 0.1
    #             sm_dict_extended[f"{iban_result.id}"] = similarity_ratio
    # if sm_dict_extended:
    #     result = sorted(
    #         sm_dict_extended.items(), key=lambda item: item[1], reverse=True
    #     )[0]
    #     if float(result[1]) >= 0.5:
    #         iban_result = BuildIbanDescription.filter_one(
    #             BuildIbanDescription.id == int(result[0]), system=True
    #         ).data
    #         return {
    #             "company_id": iban_result.company_id,
    #             "customer_id": iban_result.customer_id,
    #             "found_from": "Name",
    #             "similarity": result[1],
    #         }
    # return {
    #     "company_id": None,
    #     "customer_id": None,
    #     "found_from": None,
    #     "similarity": 0.0,
    # }


#
# def wag_insert_budget_record(data):
#     similarity_result = parse_comment_with_name(data["iban"], data["process_comment"])
#     build_iban = BuildIbans.find_one(iban=data["iban"])
#
#     if payload := InsertBudgetRecord(**data):
#         payload_dict = payload.model_dump(exclude_unset=True, exclude_none=True)
#         decision_books = BuildDecisionBook.select_only(
#             BuildDecisionBook.period_start_date
#             < strip_date_to_valid(payload_dict["bank_date"]),
#             BuildDecisionBook.period_stop_date
#             > strip_date_to_valid(payload_dict["bank_date"]),
#             select_args=[BuildDecisionBook.id],
#             order_by=[BuildDecisionBook.expiry_ends.desc()],
#         )
#         payload_dict["build_id"] = getattr(
#             BuildIbans.find_one(iban=data["iban"]), "build_id", None
#         )
#         living_space, count = BuildLivingSpace.find_living_from_customer_id(
#             similarity_result.get("customer_id", None),
#             strip_date_to_valid(payload_dict["bank_date"]),
#         )
#         # living_space, count = BuildLivingSpace.filter(
#         #     or_(
#         #         BuildLivingSpace.owner_person_id
#         #         == similarity_result.get("customer_id", None),
#         #         BuildLivingSpace.life_person_id
#         #         == similarity_result.get("customer_id", None),
#         #     ),
#         #     BuildLivingSpace.start_date
#         #     < strip_date_to_valid(payload_dict["bank_date"]) - timedelta(days=30),
#         #     BuildLivingSpace.stop_date
#         #     > strip_date_to_valid(payload_dict["bank_date"]) + timedelta(days=30),
#         #     BuildLivingSpace.active == True,
#         #     BuildLivingSpace.deleted == False,
#         # )
#         payload_dict["build_decision_book_id"] = (
#             decision_books[0][0].id if decision_books else None
#         )
#         payload_dict["company_id"] = similarity_result.get("company_id", None)
#         payload_dict["customer_id"] = similarity_result.get("customer_id", None)
#         payload_dict["send_person_id"] = similarity_result.get("send_person_id", None)
#
#         payload_dict["build_parts_id"] = (
#             living_space[0].build_parts_id if living_space else None
#         )
#
#         payload_dict["bank_date_y"] = strip_date_to_valid(
#             payload_dict["bank_date"]
#         ).year
#         payload_dict["bank_date_m"] = strip_date_to_valid(
#             payload_dict["bank_date"]
#         ).month
#         payload_dict["bank_date_d"] = strip_date_to_valid(payload_dict["bank_date"]).day
#         payload_dict["bank_date_w"] = strip_date_to_valid(
#             payload_dict["bank_date"]
#         ).isocalendar()[2]
#         payload_dict["build_id"] = build_iban.build_id if build_iban else None
#         payload_dict["replication_id"] = 55
#         payload_dict["receive_debit"] = (
#             "R" if payload_dict["currency_value"] < 0 else "D"
#         )
#         data, found = AccountRecords.find_or_create(
#             **payload_dict,
#             found_from=similarity_result.get("found_from", None),
#             similarity=similarity_result.get("similarity", 0.0),
#         )
#         data.payment_budget_record_close()
#         return data, found
