import io
import csv
import re
import time
import threading
from datetime import datetime
from urllib.parse import urlparse

import requests
import pandas as pd
from flask import Flask, render_template, request, jsonify, send_file

app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024

CDX_URL = "https://web.archive.org/cdx/search/cdx"
HEADERS = {"User-Agent": "DomainHunterSEO/1.0 (personal research tool; respectful low-volume requests)"}
audit_lock = threading.Lock()


def normalize_domain(value):
    value = (value or "").strip().lower()
    if not value:
        return ""
    if value.startswith("#"):
        return ""
    if "://" not in value:
        value = "http://" + value
    try:
        parsed = urlparse(value)
        host = (parsed.hostname or "").strip(".").lower()
    except Exception:
        return ""
    if host.startswith("www."):
        host = host[4:]
    # basic validation; supports internationalized domains as returned by urlparse
    if len(host) > 253 or "." not in host:
        return ""
    if any(not re.fullmatch(r"[a-z0-9-]{1,63}", label) for label in host.split(".")):
        return ""
    return host


def parse_upload(file_storage):
    filename = (file_storage.filename or "").lower()
    raw = file_storage.read()
    if filename.endswith(".xlsx") or filename.endswith(".xls"):
        df = pd.read_excel(io.BytesIO(raw), header=None)
        vals = df.astype(str).values.flatten().tolist()
    elif filename.endswith(".csv"):
        try:
            text = raw.decode("utf-8-sig")
        except UnicodeDecodeError:
            text = raw.decode("latin-1", errors="replace")
        # Read first column from each CSV row, but allow pasted domain lists too.
        vals = []
        for row in csv.reader(io.StringIO(text)):
            if row:
                vals.extend(row)
    else:
        try:
            text = raw.decode("utf-8-sig")
        except UnicodeDecodeError:
            text = raw.decode("latin-1", errors="replace")
        vals = re.split(r"[\s,;]+", text)
    domains = []
    seen = set()
    for val in vals:
        d = normalize_domain(str(val))
        if d and d not in seen:
            seen.add(d)
            domains.append(d)
    return domains


def fetch_wayback(domain, session, timeout=20):
    params = {
        "url": domain,
        "matchType": "domain",
        "output": "json",
        "filter": ["statuscode:200", "mimetype:text/html"],
        "collapse": "timestamp:4",
        "fl": "timestamp,original,statuscode,mimetype",
        "limit": "500"
    }
    result = {
        "first_seen": "",
        "last_seen": "",
        "active_years": 0,
        "html_snapshots": 0,
        "old_urls_count": 0,
        "wayback_status": "No records",
        "sample_old_urls": "",
        "wayback_error": ""
    }
    try:
        resp = session.get(CDX_URL, params=params, headers=HEADERS, timeout=timeout)
        if resp.status_code == 429:
            result["wayback_status"] = "Rate limited"
            result["wayback_error"] = "HTTP 429"
            return result
        resp.raise_for_status()
        payload = resp.json()
        if not isinstance(payload, list) or len(payload) <= 1:
            result["wayback_status"] = "No records"
            return result
        rows = payload[1:]
        stamps = []
        urls = []
        years = set()
        for row in rows:
            if not row or len(row) < 2:
                continue
            stamp, url = row[0], row[1]
            if len(stamp) >= 4:
                years.add(stamp[:4])
                stamps.append(stamp)
            if url:
                urls.append(url)
        if stamps:
            result["first_seen"] = min(stamps)[:8]
            result["last_seen"] = max(stamps)[:8]
            result["active_years"] = len(years)
            result["html_snapshots"] = len(rows)
            result["old_urls_count"] = len(set(urls))
            result["sample_old_urls"] = " | ".join(list(dict.fromkeys(urls))[:5])
            result["wayback_status"] = "Records found"
        return result
    except requests.exceptions.Timeout:
        result["wayback_status"] = "Error"
        result["wayback_error"] = "Timeout"
    except requests.exceptions.RequestException as e:
        result["wayback_status"] = "Error"
        result["wayback_error"] = str(e)[:180]
    except Exception as e:
        result["wayback_status"] = "Error"
        result["wayback_error"] = str(e)[:180]
    return result


def score_domain(row):
    # Score is a heuristic for prioritization, NOT proof of historical Google traffic.
    score = 0
    years = int(row.get("active_years") or 0)
    snaps = int(row.get("html_snapshots") or 0)
    urls = int(row.get("old_urls_count") or 0)
    niche = (row.get("old_niche") or "").strip().lower()
    relevance = (row.get("target_relevance") or "").strip().lower()
    traffic_evidence = (row.get("traffic_evidence") or "").strip().lower()
    spam = (row.get("spam_risk") or "").strip().lower()

    score += min(10, years * 2)                 # archive longevity, max 10
    score += min(5, snaps // 10)                # archived HTML evidence, max 5
    score += min(5, urls // 10)                 # URL diversity, max 5
    # User-entered evidence only; the free version cannot verify organic traffic.
    if traffic_evidence in {"verified", "verified traffic", "confirmed", "đã xác minh"}:
        score += 30
    elif traffic_evidence in {"strong", "strong signal", "tín hiệu mạnh"}:
        score += 15
    elif traffic_evidence in {"possible", "possible signal", "có dấu hiệu"}:
        score += 5

    if relevance in {"high", "cao", "related", "relevant"}:
        score += 20
    elif relevance in {"medium", "trung bình", "somewhat"}:
        score += 10
    elif relevance in {"low", "thấp", "unrelated"}:
        score += 0

    # Link metrics are optional manual imports; capped to prevent one metric dominating.
    try:
        rd = int(float(row.get("referring_domains") or 0))
        score += min(15, int(rd / 10))
    except (ValueError, TypeError):
        pass

    if spam in {"high", "cao", "severe", "nghiêm trọng"}:
        score -= 35
    elif spam in {"medium", "trung bình", "moderate"}:
        score -= 12

    score = max(0, min(100, score))
    if spam in {"high", "cao", "severe", "nghiêm trọng"}:
        decision = "Reject - high spam risk"
    elif traffic_evidence not in {"verified", "verified traffic", "confirmed", "đã xác minh"}:
        decision = "Needs traffic verification"
    elif score >= 80:
        decision = "Priority review"
    elif score >= 60:
        decision = "Watchlist"
    else:
        decision = "Low priority"
    return score, decision


def audit_domains(domains, user_rows=None, delay=0.35):
    user_rows = user_rows or {}
    results = []
    with requests.Session() as session:
        for idx, domain in enumerate(domains):
            row = {
                "domain": domain,
                "traffic_evidence": "",
                "historical_keywords": "",
                "referring_domains": "",
                "old_niche": "",
                "target_relevance": "",
                "spam_risk": "",
                "redirect_target": "",
            }
            row.update(user_rows.get(domain, {}))
            wb = fetch_wayback(domain, session)
            row.update(wb)
            row["traffic_classification"] = (
                "Not verified by Wayback" if wb["wayback_status"] == "Records found"
                else "No archive evidence" if wb["wayback_status"] == "No records"
                else "Audit error / retry"
            )
            score, decision = score_domain(row)
            row["score"] = score
            row["decision"] = decision
            row["audited_at"] = datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
            results.append(row)
            if idx < len(domains) - 1:
                time.sleep(delay)
    return results


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/api/audit", methods=["POST"])
def api_audit():
    if "file" not in request.files or not request.files["file"].filename:
        return jsonify({"error": "Please upload a CSV, TXT, XLSX or XLS file."}), 400
    domains = parse_upload(request.files["file"])
    if not domains:
        return jsonify({"error": "No valid domains found in the uploaded file."}), 400
    if len(domains) > 1000:
        return jsonify({"error": "Maximum 1,000 unique domains per batch in this starter version."}), 400
    if not audit_lock.acquire(blocking=False):
        return jsonify({"error": "An audit is already running. Please wait until it finishes."}), 409
    try:
        results = audit_domains(domains)
    finally:
        audit_lock.release()
    # Return compact results to browser; full details remain in this response for export.
    return jsonify({"count": len(results), "results": results})


@app.route("/api/export", methods=["POST"])
def api_export():
    payload = request.get_json(silent=True) or {}
    rows = payload.get("results", [])
    fmt = payload.get("format", "csv").lower()
    if not rows:
        return jsonify({"error": "No audit results to export."}), 400
    df = pd.DataFrame(rows)
    if fmt == "xlsx":
        output = io.BytesIO()
        with pd.ExcelWriter(output, engine="openpyxl") as writer:
            df.to_excel(writer, index=False, sheet_name="Domain Audit")
            ws = writer.sheets["Domain Audit"]
            ws.freeze_panes = "A2"
            ws.auto_filter.ref = ws.dimensions
            for col in ws.columns:
                max_len = max(len(str(c.value or "")) for c in col[:200])
                ws.column_dimensions[col[0].column_letter].width = min(max(max_len + 2, 12), 48)
        output.seek(0)
        return send_file(output, as_attachment=True, download_name="domain_hunter_audit.xlsx",
                         mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    output = io.StringIO()
    df.to_csv(output, index=False, encoding="utf-8-sig")
    binary = io.BytesIO(output.getvalue().encode("utf-8-sig"))
    return send_file(binary, as_attachment=True, download_name="domain_hunter_audit.csv",
                     mimetype="text/csv; charset=utf-8")


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