#!/usr/bin/env python3
"""Python 3 stdlib CSV → lookup API → CSV. Start with a cw_test_ key."""
import argparse
import csv
import json
import os
from pathlib import Path
import time
import urllib.error
import urllib.parse
import urllib.request
import uuid

BASE = 'https://checkwarranty.info/api/v1/devices/'

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

OPENER = urllib.request.build_opener(NoRedirect)

def request_id(run_id, index, body):
    return str(uuid.uuid5(uuid.NAMESPACE_URL, json.dumps([run_id, index, body], sort_keys=True)))

def lookup(body, key, idempotency_key):
    headers = {'X-API-Key': key, 'Idempotency-Key': idempotency_key, 'Content-Type': 'application/json'}
    request = urllib.request.Request(BASE + 'lookup', data=json.dumps(body).encode(), headers=headers, method='POST')
    with OPENER.open(request, timeout=45) as response:
        result = json.load(response)
        if response.status != 202:
            return result
        location = urllib.parse.urljoin(BASE + 'lookup', response.headers.get('Location', ''))
    target, origin = urllib.parse.urlsplit(location), urllib.parse.urlsplit(BASE)
    if (target.scheme, target.netloc) != (origin.scheme, origin.netloc) or not target.path.startswith(origin.path + 'scans/'):
        raise ValueError('Missing or unsafe polling Location')
    for _ in range(60):
        time.sleep(2)
        request = urllib.request.Request(location, headers={'X-API-Key': key}, method='GET')
        with OPENER.open(request, timeout=45) as response:
            result = json.load(response)
        if result.get('status') in ('completed', 'partial', 'failed'):
            return result
    raise TimeoutError('Still processing. Rerun the unchanged input with the same --run-id to retrieve it.')

def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('input', type=Path)
    parser.add_argument('output', type=Path)
    parser.add_argument('--run-id', required=True, help='Unique intake identifier. Reuse for retries of the unchanged file.')
    parser.add_argument('--live', action='store_true', help='Allow a live API key; useful results consume credits.')
    args = parser.parse_args()
    key = os.environ.get('CW_API_KEY', '')
    if not key or (not key.startswith('cw_test_') and not args.live):
        parser.error('Set CW_API_KEY to a cw_test_ key; live keys require --live.')
    if args.input.resolve() == args.output.resolve():
        parser.error('Input and output must be different files.')
    with args.input.open(newline='', encoding='utf-8-sig') as source:
        reader = csv.DictReader(source)
        if not {'serialNumber', 'brand'}.issubset(reader.fieldnames or []):
            parser.error('CSV requires serialNumber and brand columns; productNumber is optional.')
        rows = list(reader)
    bodies = []
    for row in rows:
        body = {field: (row.get(field) or '').strip() for field in ('serialNumber', 'brand', 'productNumber')}
        if not body['serialNumber'] or body['brand'] not in ('lenovo', 'dell', 'hp', 'apple'):
            parser.error('Every row needs a serialNumber and brand: lenovo, dell, hp or apple.')
        bodies.append({field: value for field, value in body.items() if value})
    with args.output.open('x', newline='', encoding='utf-8') as target:
        writer = csv.DictWriter(target, fieldnames=['serialNumber', 'brand', 'status', 'resultJson', 'error'])
        writer.writeheader()
        for index, body in enumerate(bodies):
            try:
                result = lookup(body, key, request_id(args.run_id, index, body))
                row = {'status': result.get('status', ''), 'resultJson': json.dumps(result, ensure_ascii=False)}
            except urllib.error.HTTPError as error:
                with error:
                    try:
                        problem = json.load(error)
                    except (ValueError, UnicodeDecodeError):
                        problem = {'title': error.reason}
                row = {'status': 'error', 'error': f'HTTP {error.code}', 'resultJson': json.dumps(problem, ensure_ascii=False)}
            except Exception as error:
                row = {'status': 'error', 'error': f'{type(error).__name__}: {error}'}
            writer.writerow({'serialNumber': body['serialNumber'], 'brand': body['brand'], **row})
            target.flush()

if __name__ == '__main__':
    main()
