import sys
from argparse import ArgumentParser
from typing import Any, NoReturn, Optional, cast

from rentry.client import RentrySyncClient, RentrySyncPage
from rentry.errors import RentryExistingPageError, RentryInvalidAuthTokenError, RentryInvalidContentLengthError, RentryInvalidCSRFError, RentryInvalidEditCodeError, RentryInvalidMetadataError, RentryInvalidPageURLError, RentryInvalidResponseError, RentryNonExistentPageError
from rentry.metadata import RentryPageMetadata

USAGE = """
Command line access to the rentry API.

#### Commands
- help: Show this help message.
- read: Get the raw markdown of a page with a SECRET_RAW_ACCESS_CODE set or if you provide an --auth-token.
    - rentry read <--page-id PAGE_ID> [--auth-token AUTH_TOKEN]
- fetch: Fetch the data for a page you have the edit code for.
    - rentry fetch <--page-id PAGE_ID> <--edit-code EDIT_CODE>
- exists: Check if a page exists.
    - rentry exists <--page-id PAGE_ID>
- create: Create a new page.
    - rentry create <--markdown MARKDOWN> [--page-id PAGE_ID] [--edit-code EDIT_CODE] [--metadata METADATA]
- update: Update a page you have the edit or modify code for.
    - rentry update <--page-id PAGE_ID> <--edit-code EDIT_CODE> [--new-page-id NEW_PAGE_ID] [--new-edit-code NEW_EDIT_CODE] [--new-modify-code NEW_MODIFY_CODE] [--markdown MARKDOWN] [--metadata METADATA] [--overwrite]
- delete: Delete a page you have the edit code for.
    - rentry delete <--page-id PAGE_ID> <--edit-code EDIT_CODE>

#### Arguments
--page-id
--edit-code
    - When used with the "update" command this can be a modify code instead.
    - Modify codes start with "m:" and do not allow updating the edit or modify codes or deleting the page.
--auth-token
    - Auth tokens are acquired by contacting rentry support.
--new-page-id
    - Must be between 2 and 100 characters.
    - Must contain only latin letters, numbers, underscores and hyphens.
    - Will cause the existing modify code to reset if set.
--new-edit-code
    - Must be between 1 and 100 characters.
    - Can't start with "m:" as that is reserved for modify codes.
--new-modify-code
    - Must start with "m:" and be between 1 and 100 characters.
    - Provide "m:" to remove the modify code.
--markdown
    - Must be between 1 and 200,000 characters.
--metadata
    - A JSON string containing '{"string": "string"}' key-value pairs.
--overwrite
    - Whether to overwrite the existing markdown and metadata with the new values.
--base-url
    - The base URL to use.
    - Defaults to "https://rentry.co" but can be set to "https://rentry.org".
    - All data is shared between the two domains.

#### Examples
- rentry read --page-id py
- rentry fetch --page-id py --edit-code pyEditCode
- rentry exists --page-id py
- rentry create --markdown "Hello, World!" --page-id py --edit-code pyEditCode
- rentry update --page-id py --edit-code pyEditCode --metadata '{"PAGE_TITLE": "Hello, World!"}' --overwrite
- rentry delete --page-id py --edit-code pyEditCode
"""


class RentryArgumentParser(ArgumentParser):
    def error(self, message) -> NoReturn:
        if "unrecognized arguments" in message:
            print('Unrecognized arguments. Use "rentry help" for more information.')
        elif all([term in message for term in ["invalid choice", "--base-url"]]):
            print('Invalid choice for --base-url. Use "rentry help" for more information.')
        else:
            print(message)
        sys.exit(2)


def main() -> None:
    client: RentrySyncClient = RentrySyncClient()
    parser: ArgumentParser = RentryArgumentParser(prog="rentry", usage=USAGE, description="Access the rentry API through the command line.")
    parser.add_argument("command", type=str, nargs="?", help="The command to run.")
    parser.add_argument("--page-id", type=str, help="The page ID to use.")
    parser.add_argument("--edit-code", type=str, help="The edit code to use.")
    parser.add_argument("--auth-token", type=str, help="The auth token to use.")
    parser.add_argument("--new-page-id", type=str, help="The new page ID to use.")
    parser.add_argument("--new-edit-code", type=str, help="The new edit code to use.")
    parser.add_argument("--new-modify-code", type=str, help="The new modify code to use.")
    parser.add_argument("--markdown", type=str, help="The markdown to use.")
    parser.add_argument("--metadata", type=str, help="The metadata to use.")
    parser.add_argument("--overwrite", action="store_true", help="Whether to overwrite the existing markdown and metadata with the new values.")
    parser.add_argument("--base-url", type=str, default="https://rentry.co", choices=["https://rentry.co", "https://rentry.org"], help="The base URL to use.")

    try:
        args: dict[str, Any] = vars(parser.parse_args())
        command: Optional[str] = args.get("command")
        page_id: Optional[str] = cast(str, pid).split("/")[-1].strip() if (pid := args.get("page_id")) is not None else None
        edit_code: Optional[str] = args.get("edit_code")
        auth_token: Optional[str] = args.get("auth_token")
        new_page_id: Optional[str] = args.get("new_page_id")
        new_edit_code: Optional[str] = args.get("new_edit_code")
        new_modify_code: Optional[str] = args.get("new_modify_code")
        markdown: Optional[str] = args.get("markdown")
        metadata: Optional[RentryPageMetadata] = RentryPageMetadata.build(mtdt) if (mtdt := args.get("metadata")) else None
        overwrite: bool = args.get("overwrite") or False
        base_url: str = args.get("base_url") or "https://rentry.co"

        if not command or command == "help":
            print(USAGE)
        elif command == "read":
            if not page_id:
                print("You must provide a page ID with the --page-id argument.")
                return
            elif edit_code or new_page_id or new_edit_code or new_modify_code or markdown or metadata or overwrite:
                print('More arguments than expected for the "read" command. Use "rentry help" for more information.')
                return

            client.auth_token = auth_token
            markdown = client.read(page_id)
            print(markdown)
        elif command == "fetch":
            if not page_id or not edit_code:
                print("You must provide a page ID with the --page-id argument and an edit code (or modify code) with the --edit-code argument.")
                return
            elif auth_token or new_page_id or new_edit_code or new_modify_code or markdown or metadata or overwrite:
                print('More arguments than expected for the "fetch" command. Use "rentry help" for more information.')
                return

            page: RentrySyncPage = client.fetch(page_id, edit_code)
            is_modify_code: bool = edit_code.lower().startswith("m:")
            f_out_page_id: str = f"├───── Page ID: {page_id}"
            f_out_edit_code: Optional[str] = f"├─── Edit Code: {edit_code}" if not is_modify_code else None
            f_out_modify_code: Optional[str] = f"├─ Modify Code: {edit_code}" if is_modify_code else None
            f_out_stats: Optional[str] = "├─────── Stats:" if page.stats else None
            f_out_metadata: str = f"├──── Metadata: {page.metadata.encode()}" if bool(page.metadata) else "├──── Metadata: None"
            f_out_markdown: Optional[str] = f"├──── Markdown:─┐\n                ↓\n{page.markdown}" if page.markdown else "├──── Markdown: None"

            f_out_modify_code_set: Optional[str] = f"│        ├── Modify Code Set: {page.stats.modify_code_set}" if page.stats else None
            f_out_published_date: Optional[str] = f"│        ├─── Published Date: {page.stats.published_date.strftime('%B %d, %Y %H:%M:%S')}" if page.stats and page.stats.published_date else None
            f_out_activated_date: Optional[str] = f"│         ├─── Activated Date: {page.stats.activated_date.strftime('%B %d, %Y %H:%M:%S')}" if page.stats and page.stats.activated_date else None
            f_out_edited_date: Optional[str] = f"│        ├────── Edited Date: {page.stats.edited_date.strftime('%B %d, %Y %H:%M:%S')}" if page.stats and page.stats.edited_date else None
            f_out_metadata_version: Optional[str] = f"│        ├─ Metadata Version: {page.stats.metadata_version}" if page.stats else None
            f_out_view_count: Optional[str] = f"│        ├─────── View Count: {page.stats.views}" if page.stats else None
            f_stats: list[str] = [stat for stat in [f_out_modify_code_set, f_out_published_date, f_out_activated_date, f_out_edited_date, f_out_metadata_version, f_out_view_count] if stat is not None]

            if f_stats:
                f_stats[-1] = "└".join(f_stats[-1].split("├", 1))
            if f_out_stats:
                f_out_stats += "\n" + "\n".join(f_stats)

            f_fetched_values: list[str] = [value for value in [f_out_page_id, f_out_edit_code, f_out_modify_code, f_out_stats, f_out_metadata, f_out_markdown] if value is not None]
            f_fetched_values[-1] = "└".join(f_fetched_values[-1].split("├", 1))
            output: str = f"\n{page.page_url} has the following data:\n" + "\n".join(f_fetched_values) + "\n"
            print(output)
        elif command == "exists":
            if not page_id:
                print("You must provide a page ID with the --page-id argument.")
                return
            elif edit_code or auth_token or new_page_id or new_edit_code or new_modify_code or markdown or metadata or overwrite:
                print('More arguments than expected for the "exists" command. Use "rentry help" for more information.')

            exists: bool = client.exists(page_id)
            print(exists)
        elif command == "create":
            if not markdown:
                print("You must provide markdown with the --markdown argument.")
                return
            elif auth_token or new_page_id or new_edit_code or new_modify_code or overwrite:
                print('More arguments than expected for the "create" command. Use "rentry help" for more information.')
                return

            page: RentrySyncPage = client.create(markdown, page_id, edit_code, metadata)
            print(f" Page URL: {page.page_url}\nEdit Code: {page.edit_code}\n Markdown: \n\n{page.markdown}")
        elif command == "delete":
            if not page_id or not edit_code:
                print("You must provide a page ID with the --page-id argument and an edit code with the --edit-code argument.")
                return
            elif auth_token or new_page_id or new_edit_code or new_modify_code or markdown or metadata or overwrite:
                print('More arguments than expected for the "delete" command. Use "rentry help" for more information.')
                return

            page: RentrySyncPage = client.delete(page_id, edit_code)
            print(f"{page.page_url} has been deleted.")
        elif command == "update":
            if not page_id or not edit_code:
                print("You must provide a page ID with the --page-id argument and an edit or modify code with the --edit-code argument.")
                return
            elif auth_token:
                print('More arguments than expected for the "update" command. Use "rentry help" for more information.')
                return

            is_modify_code: bool = edit_code.lower().startswith("m:")

            if is_modify_code and (new_page_id or new_edit_code or new_modify_code):
                print("Modify codes can't update the page ID, edit code, or modify code.")
                return

            passed_values: list[str] = [arg for arg in [new_page_id, new_edit_code, new_modify_code, markdown, metadata] if arg is not None]

            if not passed_values:
                print(f"{base_url}/{page_id} has not been updated due to no values being provided.")
                return

            page: RentrySyncPage = client.update(page_id, edit_code, new_page_id, new_edit_code, new_modify_code, markdown, metadata, overwrite)
            u_out_new_page_id: Optional[str] = f"├───── Page ID: {page.page_id}" if new_page_id else None
            u_out_new_edit_code: Optional[str] = f"├─── Edit Code: {page.edit_code}" if new_edit_code else None
            u_out_new_modify_code: Optional[str] = f"├─ Modify Code: {page.modify_code}" if new_modify_code else None
            u_out_metadata: Optional[str] = f"├──── Metadata: {page.metadata.encode()}" if bool(metadata) else None
            u_out_markdown: Optional[str] = f"├──── Markdown:─┐\n                ↓\n{page.markdown}" if page.markdown else "├──── Markdown: Cleared" if overwrite else None
            u_modified_values: list[str] = [value for value in [u_out_new_page_id, u_out_new_edit_code, u_out_new_modify_code, u_out_metadata, u_out_markdown] if value is not None]

            u_modified_values[-1] = "└".join(u_modified_values[-1].split("├", 1))
            output: str = f"\n{page.page_url} has been updated with the following values:\n" + "\n".join(u_modified_values) + "\n"
            print(output)
        else:
            print('Invalid command. Use "rentry help" for more information.')
    except (RentryExistingPageError, RentryInvalidAuthTokenError, RentryInvalidContentLengthError, RentryInvalidCSRFError, RentryInvalidEditCodeError, RentryInvalidMetadataError, RentryInvalidPageURLError, RentryInvalidResponseError, RentryNonExistentPageError) as e:
        print(str(e).replace("auth_token", "--auth-token"))
    except Exception as e:
        print(f"An unexpected error occurred: {e}")


if __name__ == "__main__":
    main()
