from __future__ import annotations

import csv
from datetime import date, datetime, timedelta
from email.message import EmailMessage
from email.utils import format_datetime, make_msgid
import hmac
import imaplib
import os
from pathlib import Path
import secrets
import smtplib
import sqlite3
import ssl
import time

from flask import Flask, abort, flash, redirect, render_template, request, session, url_for


ROOT = Path(__file__).resolve().parent
DATA_DIR = Path(os.environ.get("MARINERELAY_DATA_DIR", ROOT / "data"))
DB_PATH = DATA_DIR / "outreach.db"
SEED_LEDGER = ROOT / "seed" / "outreach-ledger.csv"
SENT_ARCHIVE = DATA_DIR / "sent-archive"

SENDER_NAME = os.environ.get("SENDER_NAME", "Braden Glasgow")
SENDER_EMAIL = os.environ.get("SENDER_EMAIL", "bglasgow@marinerelay.com")
SMTP_HOST = os.environ.get("SMTP_HOST", "mail.privateemail.com")
SMTP_PORT = int(os.environ.get("SMTP_PORT", "465"))
IMAP_HOST = os.environ.get("IMAP_HOST", "mail.privateemail.com")
IMAP_PORT = int(os.environ.get("IMAP_PORT", "993"))

app = Flask(
    __name__,
    template_folder=str(ROOT / "templates"),
    static_folder=str(ROOT / "static"),
)
app.secret_key = os.environ.get("SECRET_KEY", "")
app.config.update(
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SECURE=os.environ.get("COOKIE_SECURE", "1") == "1",
    SESSION_COOKIE_SAMESITE="Strict",
    PERMANENT_SESSION_LIFETIME=timedelta(hours=12),
    MAX_CONTENT_LENGTH=32 * 1024,
)

if not app.secret_key:
    raise RuntimeError("SECRET_KEY is not configured in the Python App environment variables.")


def db() -> sqlite3.Connection:
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    connection = sqlite3.connect(DB_PATH, timeout=20)
    connection.row_factory = sqlite3.Row
    connection.execute("PRAGMA journal_mode=WAL")
    connection.execute("PRAGMA foreign_keys=ON")
    return connection


def initialize_database() -> None:
    with db() as conn:
        conn.executescript(
            """
            CREATE TABLE IF NOT EXISTS outreach (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                company TEXT NOT NULL,
                contact_name TEXT NOT NULL DEFAULT '',
                email TEXT NOT NULL DEFAULT '',
                phone TEXT NOT NULL DEFAULT '',
                website TEXT NOT NULL DEFAULT '',
                channel TEXT NOT NULL DEFAULT 'Email',
                status TEXT NOT NULL,
                date_contacted TEXT NOT NULL DEFAULT '',
                follow_up_date TEXT NOT NULL DEFAULT '',
                subject TEXT NOT NULL DEFAULT '',
                notes TEXT NOT NULL DEFAULT '',
                message_id TEXT NOT NULL DEFAULT '',
                created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
            );
            CREATE INDEX IF NOT EXISTS idx_outreach_email ON outreach(email);
            CREATE INDEX IF NOT EXISTS idx_outreach_followup ON outreach(follow_up_date, status);

            CREATE TABLE IF NOT EXISTS drafts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                company TEXT NOT NULL,
                contact_name TEXT NOT NULL,
                email TEXT NOT NULL,
                phone TEXT NOT NULL DEFAULT '',
                website TEXT NOT NULL DEFAULT '',
                subject TEXT NOT NULL,
                body TEXT NOT NULL,
                follow_up_days INTEGER NOT NULL DEFAULT 2,
                status TEXT NOT NULL DEFAULT 'Draft',
                created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
                sent_at TEXT NOT NULL DEFAULT ''
            );
            """
        )
        count = conn.execute("SELECT COUNT(*) FROM outreach").fetchone()[0]
        if count == 0 and SEED_LEDGER.exists():
            with SEED_LEDGER.open("r", encoding="utf-8-sig", newline="") as handle:
                for row in csv.DictReader(handle):
                    conn.execute(
                        """INSERT INTO outreach
                        (company, contact_name, email, phone, website, channel, status,
                         date_contacted, follow_up_date, subject, notes, message_id)
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                        tuple(row.get(key, "") for key in (
                            "company", "contact_name", "email", "phone", "website", "channel",
                            "status", "date_contacted", "follow_up_date", "subject", "notes", "message_id"
                        )),
                    )


def configured() -> bool:
    return bool(os.environ.get("DASHBOARD_PASSWORD") and os.environ.get("MAILBOX_PASSWORD"))


def csrf_token() -> str:
    if "csrf_token" not in session:
        session["csrf_token"] = secrets.token_urlsafe(32)
    return session["csrf_token"]


def validate_csrf() -> None:
    supplied = request.form.get("csrf_token", "")
    expected = session.get("csrf_token", "")
    if not supplied or not expected or not hmac.compare_digest(supplied, expected):
        abort(400, "Invalid or expired form token.")


@app.context_processor
def template_context():
    return {"csrf_token": csrf_token, "sender_email": SENDER_EMAIL}


@app.before_request
def require_login():
    if request.endpoint in {"login", "static"}:
        return None
    if not session.get("authenticated"):
        return redirect(url_for("login"))
    return None


@app.after_request
def security_headers(response):
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["Referrer-Policy"] = "no-referrer"
    response.headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'"
    response.headers["Cache-Control"] = "no-store"
    return response


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        validate_csrf()
        expected = os.environ.get("DASHBOARD_PASSWORD", "")
        supplied = request.form.get("password", "")
        if expected and hmac.compare_digest(supplied, expected):
            session.clear()
            session["authenticated"] = True
            session.permanent = True
            return redirect(url_for("dashboard"))
        time.sleep(1)
        flash("Incorrect password.", "error")
    return render_template("login.html", configured=configured())


@app.post("/logout")
def logout():
    validate_csrf()
    session.clear()
    return redirect(url_for("login"))


@app.route("/")
def dashboard():
    today = date.today().isoformat()
    with db() as conn:
        drafts = conn.execute("SELECT * FROM drafts WHERE status = 'Draft' ORDER BY id DESC").fetchall()
        due = conn.execute(
            "SELECT * FROM outreach WHERE follow_up_date != '' AND follow_up_date <= ? AND status = 'Sent' ORDER BY follow_up_date",
            (today,),
        ).fetchall()
        recent = conn.execute("SELECT * FROM outreach ORDER BY id DESC LIMIT 10").fetchall()
        sent_count = conn.execute("SELECT COUNT(*) FROM outreach WHERE status IN ('Sent','Follow-up sent','Replied','Client')").fetchone()[0]
    return render_template("dashboard.html", drafts=drafts, due=due, recent=recent, sent_count=sent_count)


@app.route("/drafts/new", methods=["GET", "POST"])
def new_draft():
    if request.method == "POST":
        validate_csrf()
        values = {key: request.form.get(key, "").strip() for key in (
            "company", "contact_name", "email", "phone", "website", "subject", "body"
        )}
        if not all(values[key] for key in ("company", "contact_name", "email", "subject", "body")) or "@" not in values["email"]:
            flash("Company, contact, valid email, subject, and body are required.", "error")
            return render_template("draft_form.html", draft=values)
        with db() as conn:
            duplicate = conn.execute(
                "SELECT * FROM outreach WHERE lower(email) = lower(?) AND status IN ('Sent','Follow-up sent','Replied','Client','Do not contact') ORDER BY id DESC LIMIT 1",
                (values["email"],),
            ).fetchone()
            if duplicate:
                flash(f"Blocked: {values['email']} is already recorded as {duplicate['status']} for {duplicate['company']}.", "error")
                return render_template("draft_form.html", draft=values)
            cursor = conn.execute(
                """INSERT INTO drafts (company, contact_name, email, phone, website, subject, body)
                   VALUES (?, ?, ?, ?, ?, ?, ?)""",
                tuple(values[key] for key in ("company", "contact_name", "email", "phone", "website", "subject", "body")),
            )
            draft_id = cursor.lastrowid
        return redirect(url_for("review_draft", draft_id=draft_id))
    return render_template("draft_form.html", draft={})


@app.route("/drafts/<int:draft_id>")
def review_draft(draft_id: int):
    with db() as conn:
        draft = conn.execute("SELECT * FROM drafts WHERE id = ?", (draft_id,)).fetchone()
    if not draft:
        abort(404)
    return render_template("review.html", draft=draft)


def find_sent_folder(imap: imaplib.IMAP4_SSL) -> str:
    status, folders = imap.list()
    decoded = [item.decode(errors="replace") for item in folders or []] if status == "OK" else []
    for line in decoded:
        if "\\Sent" in line:
            return line.split(' "/" ', 1)[-1].strip().strip('"')
    return "Sent"


def next_business_day(start: date, days: int) -> date:
    result = start + timedelta(days=days)
    while result.weekday() >= 5:
        result += timedelta(days=1)
    return result


@app.post("/drafts/<int:draft_id>/send")
def send_draft(draft_id: int):
    validate_csrf()
    if request.form.get("confirmation") != "SEND":
        flash("Enter SEND to approve this exact message.", "error")
        return redirect(url_for("review_draft", draft_id=draft_id))
    password = os.environ.get("MAILBOX_PASSWORD", "")
    if not password:
        flash("MAILBOX_PASSWORD is not configured.", "error")
        return redirect(url_for("review_draft", draft_id=draft_id))

    with db() as conn:
        conn.execute("BEGIN IMMEDIATE")
        draft = conn.execute("SELECT * FROM drafts WHERE id = ? AND status = 'Draft'", (draft_id,)).fetchone()
        if not draft:
            abort(409, "This draft was already sent or removed.")
        duplicate = conn.execute(
            "SELECT * FROM outreach WHERE lower(email) = lower(?) AND status IN ('Sent','Follow-up sent','Replied','Client','Do not contact') LIMIT 1",
            (draft["email"],),
        ).fetchone()
        if duplicate:
            abort(409, "Duplicate recipient blocked by the outreach ledger.")

        msg = EmailMessage()
        msg["From"] = f"{SENDER_NAME} <{SENDER_EMAIL}>"
        msg["To"] = draft["email"]
        msg["Subject"] = draft["subject"]
        msg["Date"] = format_datetime(datetime.now().astimezone())
        msg["Message-ID"] = make_msgid(domain=SENDER_EMAIL.split("@", 1)[1])
        msg.set_content(draft["body"].rstrip() + "\n")

        context = ssl.create_default_context()
        with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, context=context, timeout=30) as smtp:
            smtp.login(SENDER_EMAIL, password)
            smtp.send_message(msg)

        SENT_ARCHIVE.mkdir(parents=True, exist_ok=True)
        archive = SENT_ARCHIVE / f"{datetime.now():%Y%m%d-%H%M%S}-{draft['id']}.eml"
        archive.write_bytes(msg.as_bytes())

        note = ""
        try:
            with imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT) as imap:
                imap.login(SENDER_EMAIL, password)
                status, _ = imap.append(find_sent_folder(imap), "\\Seen", imaplib.Time2Internaldate(time.time()), msg.as_bytes())
                if status != "OK":
                    note = "Sent; IMAP Sent-folder copy returned a non-OK status."
                imap.logout()
        except Exception as exc:
            note = f"Sent; could not add IMAP Sent copy: {type(exc).__name__}"

        contacted = date.today()
        follow_up = next_business_day(contacted, int(draft["follow_up_days"]))
        conn.execute(
            """INSERT INTO outreach
               (company, contact_name, email, phone, website, channel, status, date_contacted,
                follow_up_date, subject, notes, message_id)
               VALUES (?, ?, ?, ?, ?, 'Email', 'Sent', ?, ?, ?, ?, ?)""",
            (draft["company"], draft["contact_name"], draft["email"], draft["phone"], draft["website"],
             contacted.isoformat(), follow_up.isoformat(), draft["subject"], note, str(msg["Message-ID"])),
        )
        conn.execute("UPDATE drafts SET status = 'Sent', sent_at = CURRENT_TIMESTAMP WHERE id = ?", (draft_id,))

    flash(f"Sent to {draft['email']} and logged. Follow-up: {follow_up:%B %d, %Y}.", "success")
    return redirect(url_for("dashboard"))


@app.post("/outreach/<int:outreach_id>/status")
def update_status(outreach_id: int):
    validate_csrf()
    status = request.form.get("status", "")
    allowed = {"Sent", "Replied", "Client", "Do not contact", "Follow-up sent"}
    if status not in allowed:
        abort(400)
    with db() as conn:
        conn.execute("UPDATE outreach SET status = ? WHERE id = ?", (status, outreach_id))
    flash("Status updated.", "success")
    return redirect(url_for("dashboard"))


initialize_database()


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=False)
