from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Optional


ConfirmFn = Callable[[str], bool]


@dataclass
class LinkedInApplyResult:
    url: str
    submitted: bool
    notes: str = ""
    screenshots_dir: Optional[Path] = None


class LinkedInEasyApplyApplier:
    """
    Best-effort Easy Apply automation with mandatory user confirmation before final submit.

    Notes:
    - LinkedIn's DOM changes frequently.
    - You may need to tweak selectors or rely on manual continuation.
    """

    def __init__(
        self,
        *,
        resume_file: Path,
        cover_letter_file: Optional[Path] = None,
        screenshots_dir: Optional[Path] = None,
    ) -> None:
        self.resume_file = resume_file
        self.cover_letter_file = cover_letter_file
        self.screenshots_dir = screenshots_dir

    def apply(
        self,
        *,
        page,
        job_url: str,
        confirm_before_final_submit: ConfirmFn,
    ) -> LinkedInApplyResult:
        job_url = job_url.strip()
        page.goto(job_url, wait_until="domcontentloaded")

        if self.screenshots_dir:
            self.screenshots_dir.mkdir(parents=True, exist_ok=True)

        def snap(name: str) -> None:
            if not self.screenshots_dir:
                return
            path = self.screenshots_dir / f"{name}.png"
            try:
                page.screenshot(path=str(path), full_page=True)
            except Exception:
                # Screenshot failures shouldn't kill the workflow.
                pass

        snap("01_loaded")

        # Click "Easy Apply" if present.
        easy_apply = page.get_by_role("button", name=re.compile(r"easy apply", re.I)).first
        if easy_apply.count() == 0:
            snap("02_no_easy_apply")
            return LinkedInApplyResult(url=job_url, submitted=False, notes="Easy Apply button not found.")

        easy_apply.click()
        snap("03_clicked_easy_apply")

        # Iterate through steps. We stop when no further "Next"/"Continue" is found
        # and a submit button appears.
        submitted = False
        attempts = 0
        while attempts < 12 and not submitted:
            attempts += 1

            # Upload resume if a file input is visible.
            file_inputs = page.locator('input[type="file"]')
            if file_inputs.count() > 0:
                # Prefer first file input for resume. If multiple, attempt cover letter too.
                try:
                    file_inputs.nth(0).set_input_files(str(self.resume_file))
                    snap("04_uploaded_resume")
                except Exception:
                    snap("04_upload_resume_failed")

                if self.cover_letter_file and file_inputs.count() > 1:
                    try:
                        file_inputs.nth(1).set_input_files(str(self.cover_letter_file))
                        snap("05_uploaded_cover_letter")
                    except Exception:
                        snap("05_upload_cover_letter_failed")

            # Click "Next"/"Continue" if present.
            next_btn = page.get_by_role("button", name=re.compile(r"^(next|continue)$", re.I)).first
            if next_btn.count() > 0:
                try:
                    next_btn.click()
                    snap(f"step_{attempts:02d}_next_clicked")
                    continue
                except Exception:
                    snap(f"step_{attempts:02d}_next_failed")

            # If we reached a submit stage, confirm and click.
            submit_btn = page.get_by_role(
                "button",
                name=re.compile(r"(submit application|submit|review application|review)", re.I),
            ).first

            if submit_btn.count() > 0:
                snap(f"step_{attempts:02d}_before_submit")
                msg = f"About to submit application on LinkedIn for: {job_url}\nDo you want to submit now?"
                if confirm_before_final_submit(msg):
                    try:
                        submit_btn.click()
                        snap(f"step_{attempts:02d}_submitted_clicked")
                        submitted = True
                    except Exception as e:
                        snap(f"step_{attempts:02d}_submit_failed")
                        return LinkedInApplyResult(
                            url=job_url,
                            submitted=False,
                            notes=f"Submit click failed: {e}",
                            screenshots_dir=self.screenshots_dir,
                        )
                else:
                    snap(f"step_{attempts:02d}_submit_cancelled_by_user")
                    return LinkedInApplyResult(
                        url=job_url, submitted=False, notes="User declined final submission.", screenshots_dir=self.screenshots_dir
                    )

            # Otherwise, bail with helpful note.
            snap(f"step_{attempts:02d}_bailing")
            return LinkedInApplyResult(
                url=job_url,
                submitted=False,
                notes="Could not find next-step or submit button; likely requires manual completion.",
                screenshots_dir=self.screenshots_dir,
            )

        return LinkedInApplyResult(
            url=job_url,
            submitted=submitted,
            notes="Reached step limit or submitted flag state.",
            screenshots_dir=self.screenshots_dir,
        )

